From 04fc2cdce5905a028ec1f56dc87f58d200176e7a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Feb 2019 22:05:47 +0200 Subject: [PATCH 01/71] [iOS] Attempt to fix text-anchor subtree advance/extent calculation Related to https://github.com/react-native-community/react-native-svg/issues/600 https://github.com/react-native-community/react-native-svg/issues/570 --- ios/Text/RNSVGTSpan.m | 63 +++++++++++++++++++++++++++++++++++++++++-- ios/Text/RNSVGText.h | 2 ++ ios/Text/RNSVGText.m | 41 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 70b11adc..d52f780a 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -100,6 +100,65 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; return path; } +- (CGFloat)getSubtreeTextChunksTotalAdvance +{ + CGFloat advance = 0; + + NSString *str = self.content; + if (!str) { + for (UIView *node in self.subviews) { + if ([node isKindOfClass:[RNSVGText class]]) { + RNSVGText *text = (RNSVGText*)node; + advance += [text getSubtreeTextChunksTotalAdvance]; + } + } + return advance; + } + + // Create a dictionary for this font + CTFontRef fontRef = [self getFontFromContext]; + RNSVGGlyphContext* gc = [self.textRoot getGlyphContext]; + RNSVGFontData* font = [gc getFont]; + + CGFloat letterSpacing = font->letterSpacing; + bool autoKerning = !font->manualKerning; + + bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == RNSVGFontVariantLigaturesNormal; + + NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init]; + + NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1]; + attrs[NSLigatureAttributeName] = lig; + CFDictionaryRef attributes; + if (fontRef != nil) { + attrs[NSFontAttributeName] = (__bridge id)fontRef; + } + if (!autoKerning) { + NSNumber *noAutoKern = [NSNumber numberWithFloat:0.0f]; + +#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES + if (___useiOS6Attributes) + { + [attrs setObject:noAutoKern forKey:NSKernAttributeName]; + } + else +#endif + { + [attrs setObject:noAutoKern forKey:(id)kCTKernAttributeName]; + } + } + + attributes = (__bridge CFDictionaryRef)attrs; + + CFStringRef string = (__bridge CFStringRef)str; + CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); + CTLineRef line = CTLineCreateWithAttributedString(attrString); + + CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); + CGFloat textMeasure = CGRectGetWidth(textBounds); + return textMeasure; +} + - (CGPathRef)getLinePath:(NSString *)str context:(CGContextRef)context { // Create a dictionary for this font @@ -292,8 +351,8 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. */ enum RNSVGTextAnchor textAnchor = font->textAnchor; - CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); - CGFloat textMeasure = CGRectGetWidth(textBounds); + RNSVGText *anchorRoot = [self getTextAnchorRoot]; + CGFloat textMeasure = [anchorRoot getSubtreeTextChunksTotalAdvance]; CGFloat offset = [RNSVGTSpan getTextAnchorOffset:textAnchor width:textMeasure]; bool hasTextPath = textPath != nil; diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index 138f18f9..913ce61d 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -23,5 +23,7 @@ - (CGPathRef)getGroupPath:(CGContextRef)context; - (CTFontRef)getFontFromContext; +- (CGFloat)getSubtreeTextChunksTotalAdvance; +- (RNSVGText*)getTextAnchorRoot; @end diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 7efc8cf0..94625158 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -18,6 +18,7 @@ RNSVGGlyphContext *_glyphContext; NSString *_alignmentBaseline; NSString *_baselineShift; + CGFloat cachedAdvance; } - (void)invalidate @@ -25,6 +26,7 @@ if (self.dirty || self.merging) { return; } + cachedAdvance = NAN; [super invalidate]; [self clearChildCache]; } @@ -248,4 +250,43 @@ return [[self.textRoot getGlyphContext] getGlyphFont]; } +- (RNSVGText*)getTextAnchorRoot +{ + RNSVGGlyphContext* gc = [self.textRoot getGlyphContext]; + RNSVGFontData* font = [gc getFont]; + enum RNSVGTextAnchor textAnchor = font->textAnchor; + if (textAnchor == RNSVGTextAnchorStart) { + return self; + } + UIView* parent = [self superview]; + if ([parent isKindOfClass:[RNSVGText class]]) { + RNSVGText *parentText = (RNSVGText*)parent; + RNSVGGlyphContext* gc = [parentText.textRoot getGlyphContext]; + RNSVGFontData* font = [gc getFont]; + enum RNSVGTextAnchor textAnchor = font->textAnchor; + if (textAnchor == RNSVGTextAnchorStart) { + return self; + } else { + return [parentText getTextAnchorRoot]; + } + } + return self; +} + +- (CGFloat)getSubtreeTextChunksTotalAdvance +{ + if (!isnan(cachedAdvance)) { + return cachedAdvance; + } + CGFloat advance = 0; + for (UIView *node in self.subviews) { + if ([node isKindOfClass:[RNSVGText class]]) { + RNSVGText *text = (RNSVGText*)node; + advance += [text getSubtreeTextChunksTotalAdvance]; + } + } + cachedAdvance = advance; + return advance; +} + @end From 60a7b1fd53a26e2960c34b2aae7d5861d9cb4045 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Feb 2019 22:51:22 +0200 Subject: [PATCH 02/71] [Android] Attempt to fix text-anchor subtree advance/extent calculation Related to https://github.com/react-native-community/react-native-svg/issues/600 https://github.com/react-native-community/react-native-svg/issues/570 --- .../main/java/com/horcrux/svg/TSpanView.java | 49 ++++++++++++++++++- .../main/java/com/horcrux/svg/TextView.java | 41 ++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java index 32248517..2695425e 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanView.java +++ b/android/src/main/java/com/horcrux/svg/TSpanView.java @@ -21,6 +21,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.os.Build; +import android.view.View; import android.view.ViewParent; import com.facebook.react.bridge.ReactContext; @@ -106,6 +107,50 @@ class TSpanView extends TextView { return mPath; } + double getSubtreeTextChunksTotalAdvance(Paint paint) { + double advance = 0; + + if (mContent == null) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof TextView) { + TextView text = (TextView)child; + advance += text.getSubtreeTextChunksTotalAdvance(paint); + } + } + return advance; + } + + String line = mContent; + final int length = line.length(); + + if (length == 0) { + return advance; + } + + GlyphContext gc = getTextRootGlyphContext(); + FontData font = gc.getFont(); + applyTextPropertiesToPaint(paint, font); + + double letterSpacing = font.letterSpacing; + final boolean allowOptionalLigatures = letterSpacing == 0 && + font.fontVariantLigatures == FontVariantLigatures.normal; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + String required = "'rlig', 'liga', 'clig', 'calt', 'locl', 'ccmp', 'mark', 'mkmk',"; + String defaultFeatures = required + "'kern', "; + if (allowOptionalLigatures) { + String additionalLigatures = "'hlig', 'cala', "; + paint.setFontFeatureSettings(defaultFeatures + additionalLigatures + font.fontFeatureSettings); + } else { + String disableDiscretionaryLigatures = "'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, "; + paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings); + } + } + + return paint.measureText(line); + } + @SuppressWarnings("ConstantConditions") private Path getLinePath(String line, Paint paint, Canvas canvas) { final int length = line.length(); @@ -311,8 +356,10 @@ class TSpanView extends TextView { attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. */ final TextAnchor textAnchor = font.textAnchor; - final double textMeasure = paint.measureText(line); + TextView anchorRoot = getTextAnchorRoot(); + final double textMeasure = anchorRoot.getSubtreeTextChunksTotalAdvance(paint); double offset = getTextAnchorOffset(textAnchor, textMeasure); + applyTextPropertiesToPaint(paint, font); int side = 1; double startOfRendering = 0; diff --git a/android/src/main/java/com/horcrux/svg/TextView.java b/android/src/main/java/com/horcrux/svg/TextView.java index 22be697d..d1600851 100644 --- a/android/src/main/java/com/horcrux/svg/TextView.java +++ b/android/src/main/java/com/horcrux/svg/TextView.java @@ -14,6 +14,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Region; +import android.view.View; import android.view.ViewParent; import com.facebook.react.bridge.Dynamic; @@ -38,6 +39,7 @@ class TextView extends GroupView { @Nullable ArrayList mRotate; @Nullable ArrayList mDeltaX; @Nullable ArrayList mDeltaY; + double cachedAdvance = Double.NaN; public TextView(ReactContext reactContext) { super(reactContext); @@ -48,6 +50,7 @@ class TextView extends GroupView { if (mPath == null) { return; } + cachedAdvance = Double.NaN; super.invalidate(); clearChildCache(); } @@ -207,4 +210,42 @@ class TextView extends GroupView { boolean isTextNode = !(this instanceof TextPathView) && !(this instanceof TSpanView); getTextRootGlyphContext().pushContext(isTextNode, this, mFont, mPositionX, mPositionY, mDeltaX, mDeltaY, mRotate); } + + TextView getTextAnchorRoot() { + GlyphContext gc = getTextRootGlyphContext(); + FontData font = gc.getFont(); + TextProperties.TextAnchor textAnchor = font.textAnchor; + if (textAnchor == TextProperties.TextAnchor.start) { + return this; + } + ViewParent parent = this.getParent(); + if (parent instanceof TextView) { + TextView parentText = (TextView)parent; + GlyphContext parentGc = parentText.getGlyphContext(); + FontData parentFont = parentGc.getFont(); + if (parentFont.textAnchor == TextProperties.TextAnchor.start) { + return this; + } else { + return parentText.getTextAnchorRoot(); + } + } + return this; + } + + double getSubtreeTextChunksTotalAdvance(Paint paint) { + if (!Double.isNaN(cachedAdvance)) { + return cachedAdvance; + } + double advance = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof TextView) { + TextView text = (TextView)child; + advance += text.getSubtreeTextChunksTotalAdvance(paint); + } + } + cachedAdvance = advance; + return advance; + } + } From d5fa136a0fd7bffcd05c4c406caa8c26ebf39bf1 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Feb 2019 01:47:46 +0200 Subject: [PATCH 03/71] Fix text-anchor subtree advance/extent calculation for letter-spacing Related to https://github.com/react-native-community/react-native-svg/issues/570 --- .../java/com/horcrux/svg/GlyphContext.java | 2 +- .../main/java/com/horcrux/svg/TSpanView.java | 7 ++++-- .../main/java/com/horcrux/svg/TextView.java | 24 +++++++------------ ios/Text/RNSVGTSpan.m | 21 ++++++++-------- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 890f2869..d42bff93 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -19,7 +19,7 @@ import javax.annotation.Nullable; class GlyphContext { // Current stack (one per node push/pop) - private final ArrayList mFontContext = new ArrayList<>(); + final ArrayList mFontContext = new ArrayList<>(); // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java index 2695425e..9103c215 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanView.java +++ b/android/src/main/java/com/horcrux/svg/TSpanView.java @@ -146,6 +146,7 @@ class TSpanView extends TextView { String disableDiscretionaryLigatures = "'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, "; paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings); } + paint.setLetterSpacing((float)(letterSpacing / (font.fontSize * mScale))); } return paint.measureText(line); @@ -610,7 +611,7 @@ class TSpanView extends TextView { // this will just retrieve the bounding rect for 'x' paint.getTextBounds("x", 0, 1, bounds); int xHeight = bounds.height(); - baselineShift = xHeight / 2; + baselineShift = xHeight / 2.0; break; case central: @@ -738,7 +739,6 @@ class TSpanView extends TextView { final Matrix end = new Matrix(); final float[] startPointMatrixData = new float[9]; - final float[] midPointMatrixData = new float[9]; final float[] endPointMatrixData = new float[9]; emoji.clear(); @@ -1008,6 +1008,9 @@ class TSpanView extends TextView { paint.setTypeface(typeface); paint.setTextSize((float) fontSize); paint.setTextAlign(Paint.Align.LEFT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + paint.setLetterSpacing(0); + } // Do these have any effect for anyone? Not for me (@msand) at least. // paint.setUnderlineText(underlineText); diff --git a/android/src/main/java/com/horcrux/svg/TextView.java b/android/src/main/java/com/horcrux/svg/TextView.java index d1600851..5d17f772 100644 --- a/android/src/main/java/com/horcrux/svg/TextView.java +++ b/android/src/main/java/com/horcrux/svg/TextView.java @@ -213,23 +213,17 @@ class TextView extends GroupView { TextView getTextAnchorRoot() { GlyphContext gc = getTextRootGlyphContext(); - FontData font = gc.getFont(); - TextProperties.TextAnchor textAnchor = font.textAnchor; - if (textAnchor == TextProperties.TextAnchor.start) { - return this; - } + ArrayList font = gc.mFontContext; + TextView node = this; ViewParent parent = this.getParent(); - if (parent instanceof TextView) { - TextView parentText = (TextView)parent; - GlyphContext parentGc = parentText.getGlyphContext(); - FontData parentFont = parentGc.getFont(); - if (parentFont.textAnchor == TextProperties.TextAnchor.start) { - return this; - } else { - return parentText.getTextAnchorRoot(); + for (int i = font.size() - 1; i >= 0; i--) { + if (!(parent instanceof TextView) || font.get(i).textAnchor == TextProperties.TextAnchor.start) { + return node; } + node = (TextView) parent; + parent = node.getParent(); } - return this; + return node; } double getSubtreeTextChunksTotalAdvance(Paint paint) { @@ -240,7 +234,7 @@ class TextView extends GroupView { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child instanceof TextView) { - TextView text = (TextView)child; + TextView text = (TextView) child; advance += text.getSubtreeTextChunksTotalAdvance(paint); } } diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index d52f780a..a219f89f 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -121,7 +121,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; RNSVGFontData* font = [gc getFont]; CGFloat letterSpacing = font->letterSpacing; - bool autoKerning = !font->manualKerning; + CGFloat kerning = font->kerning; bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == RNSVGFontVariantLigaturesNormal; @@ -133,19 +133,18 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; if (fontRef != nil) { attrs[NSFontAttributeName] = (__bridge id)fontRef; } - if (!autoKerning) { - NSNumber *noAutoKern = [NSNumber numberWithFloat:0.0f]; + float kern = (float)(letterSpacing + kerning); + NSNumber *kernAttr = [NSNumber numberWithFloat:kern]; #if DTCORETEXT_SUPPORT_NS_ATTRIBUTES - if (___useiOS6Attributes) - { - [attrs setObject:noAutoKern forKey:NSKernAttributeName]; - } - else + if (___useiOS6Attributes) + { + [attrs setObject:kernAttr forKey:NSKernAttributeName]; + } + else #endif - { - [attrs setObject:noAutoKern forKey:(id)kCTKernAttributeName]; - } + { + [attrs setObject:kernAttr forKey:(id)kCTKernAttributeName]; } attributes = (__bridge CFDictionaryRef)attrs; From 66241b7a25e4ccb4e93882686a89e9813c201cfd Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Feb 2019 01:47:46 +0200 Subject: [PATCH 04/71] [iOS] Simplify text subtree advance calculation / anchor root resolution --- ios/Text/RNSVGGlyphContext.h | 1 + ios/Text/RNSVGGlyphContext.m | 3 +++ ios/Text/RNSVGText.m | 25 ++++++++++--------------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/ios/Text/RNSVGGlyphContext.h b/ios/Text/RNSVGGlyphContext.h index 723b12fc..de49110e 100644 --- a/ios/Text/RNSVGGlyphContext.h +++ b/ios/Text/RNSVGGlyphContext.h @@ -44,5 +44,6 @@ - (void)pushContext:(RNSVGGroup*)node font:(NSDictionary *)font; +- (NSArray*)getFontContext; @end diff --git a/ios/Text/RNSVGGlyphContext.m b/ios/Text/RNSVGGlyphContext.m index 93056dcd..43f30a12 100644 --- a/ios/Text/RNSVGGlyphContext.m +++ b/ios/Text/RNSVGGlyphContext.m @@ -107,6 +107,9 @@ @implementation RNSVGGlyphContext +- (NSArray*)getFontContext { + return mFontContext_; +} - (CTFontRef)getGlyphFont { diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 94625158..48f52e97 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -253,24 +253,19 @@ - (RNSVGText*)getTextAnchorRoot { RNSVGGlyphContext* gc = [self.textRoot getGlyphContext]; - RNSVGFontData* font = [gc getFont]; - enum RNSVGTextAnchor textAnchor = font->textAnchor; - if (textAnchor == RNSVGTextAnchorStart) { - return self; - } + NSArray* font = [gc getFontContext]; + RNSVGText* node = self; UIView* parent = [self superview]; - if ([parent isKindOfClass:[RNSVGText class]]) { - RNSVGText *parentText = (RNSVGText*)parent; - RNSVGGlyphContext* gc = [parentText.textRoot getGlyphContext]; - RNSVGFontData* font = [gc getFont]; - enum RNSVGTextAnchor textAnchor = font->textAnchor; - if (textAnchor == RNSVGTextAnchorStart) { - return self; - } else { - return [parentText getTextAnchorRoot]; + for (NSInteger i = [font count] - 1; i >= 0; i--) { + RNSVGFontData* fontData = [font objectAtIndex:i]; + if (![parent isKindOfClass:[RNSVGText class]] || + fontData->textAnchor == RNSVGTextAnchorStart) { + return node; } + node = (RNSVGText*) parent; + parent = [node superview]; } - return self; + return node; } - (CGFloat)getSubtreeTextChunksTotalAdvance From e69d2320b247af0b99113f3529be4dee275215f6 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Feb 2019 02:52:15 +0200 Subject: [PATCH 05/71] Improve text subtree advance calculation caching --- android/src/main/java/com/horcrux/svg/TSpanView.java | 8 +++++++- android/src/main/java/com/horcrux/svg/TextView.java | 6 +++++- .../src/main/java/com/horcrux/svg/VirtualView.java | 2 +- ios/RNSVGNode.h | 2 ++ ios/Text/RNSVGTSpan.m | 12 ++++++++++++ ios/Text/RNSVGText.m | 7 ++++++- 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java index 9103c215..def6d4fd 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanView.java +++ b/android/src/main/java/com/horcrux/svg/TSpanView.java @@ -108,6 +108,9 @@ class TSpanView extends TextView { } double getSubtreeTextChunksTotalAdvance(Paint paint) { + if (!Double.isNaN(cachedAdvance)) { + return cachedAdvance; + } double advance = 0; if (mContent == null) { @@ -118,6 +121,7 @@ class TSpanView extends TextView { advance += text.getSubtreeTextChunksTotalAdvance(paint); } } + cachedAdvance = advance; return advance; } @@ -125,6 +129,7 @@ class TSpanView extends TextView { final int length = line.length(); if (length == 0) { + cachedAdvance = 0; return advance; } @@ -149,7 +154,8 @@ class TSpanView extends TextView { paint.setLetterSpacing((float)(letterSpacing / (font.fontSize * mScale))); } - return paint.measureText(line); + cachedAdvance = paint.measureText(line); + return cachedAdvance; } @SuppressWarnings("ConstantConditions") diff --git a/android/src/main/java/com/horcrux/svg/TextView.java b/android/src/main/java/com/horcrux/svg/TextView.java index 5d17f772..5aa98ec9 100644 --- a/android/src/main/java/com/horcrux/svg/TextView.java +++ b/android/src/main/java/com/horcrux/svg/TextView.java @@ -50,11 +50,15 @@ class TextView extends GroupView { if (mPath == null) { return; } - cachedAdvance = Double.NaN; super.invalidate(); clearChildCache(); } + void clearCache() { + cachedAdvance = Double.NaN; + super.clearCache(); + } + @ReactProp(name = "textLength") public void setTextLength(Dynamic length) { mTextLength = SVGLength.from(length); diff --git a/android/src/main/java/com/horcrux/svg/VirtualView.java b/android/src/main/java/com/horcrux/svg/VirtualView.java index 07d28da0..933ab5ad 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualView.java +++ b/android/src/main/java/com/horcrux/svg/VirtualView.java @@ -101,7 +101,7 @@ abstract public class VirtualView extends ReactViewGroup { super.invalidate(); } - private void clearCache() { + void clearCache() { canvasDiagonal = -1; canvasHeight = -1; canvasWidth = -1; diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index bb94d891..83dde07e 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -118,4 +118,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; - (void)clearChildCache; +- (void)clearPath; + @end diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index a219f89f..11a0e98c 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -25,6 +25,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; BOOL isClosed; NSMutableArray *emoji; NSMutableArray *emojiTransform; + CGFloat cachedAdvance; } - (id)init @@ -41,6 +42,12 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; return self; } +- (void)clearPath +{ + [super clearPath]; + cachedAdvance = NAN; +} + - (void)setContent:(NSString *)content { if ([content isEqualToString:_content]) { @@ -102,6 +109,9 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; - (CGFloat)getSubtreeTextChunksTotalAdvance { + if (!isnan(cachedAdvance)) { + return cachedAdvance; + } CGFloat advance = 0; NSString *str = self.content; @@ -112,6 +122,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; advance += [text getSubtreeTextChunksTotalAdvance]; } } + cachedAdvance = advance; return advance; } @@ -155,6 +166,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); CGFloat textMeasure = CGRectGetWidth(textBounds); + cachedAdvance = textMeasure; return textMeasure; } diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 48f52e97..b9e69996 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -26,11 +26,16 @@ if (self.dirty || self.merging) { return; } - cachedAdvance = NAN; [super invalidate]; [self clearChildCache]; } +- (void)clearPath +{ + [super clearPath]; + cachedAdvance = NAN; +} + - (void)setTextLength:(RNSVGLength *)textLength { if ([textLength isEqualTo:_textLength]) { From 131ddb6a1adf52e8d5f76890da6b2ab1f7f8cccf Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Feb 2019 03:42:18 +0200 Subject: [PATCH 06/71] Implement version of toDataURL with width and height options https://github.com/react-native-community/react-native-svg/issues/855 --- .../main/java/com/horcrux/svg/SvgView.java | 14 ++++++++++ .../java/com/horcrux/svg/SvgViewModule.java | 16 ++++++++++++ elements/Svg.js | 13 +++++++--- ios/Elements/RNSVGSvgView.h | 2 ++ ios/Elements/RNSVGSvgView.m | 11 +++++++- ios/ViewManagers/RNSVGSvgViewManager.m | 26 +++++++++++++++++++ 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 90d855db..4252f703 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -306,6 +306,20 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC return Base64.encodeToString(bitmapBytes, Base64.DEFAULT); } + String toDataURL(int width, int height) { + Bitmap bitmap = Bitmap.createBitmap( + width, + height, + Bitmap.Config.ARGB_8888); + + drawChildren(new Canvas(bitmap)); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + bitmap.recycle(); + byte[] bitmapBytes = stream.toByteArray(); + return Base64.encodeToString(bitmapBytes, Base64.DEFAULT); + } + void enableTouchEvents() { if (!mResponsible) { mResponsible = true; diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java index b9ef4c03..a7573551 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; class SvgViewModule extends ReactContextBaseJavaModule { SvgViewModule(ReactApplicationContext reactContext) { @@ -33,4 +34,19 @@ class SvgViewModule extends ReactContextBaseJavaModule { successCallback.invoke(svg.toDataURL()); } } + + + @ReactMethod + public void toDataURL(int tag, ReadableMap options, Callback successCallback) { + SvgView svg = SvgViewManager.getSvgViewByTag(tag); + + if (svg != null) { + successCallback.invoke( + svg.toDataURL( + options.getInt("width"), + options.getInt("height") + ) + ); + } + } } diff --git a/elements/Svg.js b/elements/Svg.js index 1bac2f4a..30f7cbbc 100644 --- a/elements/Svg.js +++ b/elements/Svg.js @@ -49,9 +49,16 @@ export default class Svg extends Shape { this.root.setNativeProps(props); }; - toDataURL = callback => { - callback && - RNSVGSvgViewManager.toDataURL(findNodeHandle(this.root), callback); + toDataURL = (callback, options) => { + if (!callback) { + return; + } + const handle = findNodeHandle(this.root); + if (options) { + RNSVGSvgViewManager.toDataURL(handle, options, callback); + } else { + RNSVGSvgViewManager.toDataURL(handle, callback); + } }; render() { diff --git a/ios/Elements/RNSVGSvgView.h b/ios/Elements/RNSVGSvgView.h index 99112606..9ba992cf 100644 --- a/ios/Elements/RNSVGSvgView.h +++ b/ios/Elements/RNSVGSvgView.h @@ -52,6 +52,8 @@ - (NSString *)getDataURL; +- (NSString *)getDataURLwithBounds:(CGSize)bounds; + - (CGRect)getContextBounds; - (void)drawRect:(CGRect)rect; diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index 8b71ba30..f7c10826 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -247,7 +247,6 @@ return nil; } - - (NSString *)getDataURL { UIGraphicsBeginImageContextWithOptions(_boundingBox.size, NO, 0); @@ -258,6 +257,16 @@ return base64; } +- (NSString *)getDataURLwithBounds:(CGSize)bounds +{ + UIGraphicsBeginImageContextWithOptions(bounds, NO, 0); + [self drawRect:_boundingBox]; + NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); + NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; + UIGraphicsEndImageContext(); + return base64; +} + - (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor { self.backgroundColor = inheritedBackgroundColor; diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m index ae32ae60..97489a1c 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.m +++ b/ios/ViewManagers/RNSVGSvgViewManager.m @@ -43,4 +43,30 @@ RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag callback:(RCTResponseSe }]; } +RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback) +{ + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + __kindof UIView *view = viewRegistry[reactTag]; + if ([view isKindOfClass:[RNSVGSvgView class]]) { + RNSVGSvgView *svg = view; + id width = [options objectForKey:@"width"]; + id height = [options objectForKey:@"height"]; + if (![width isKindOfClass:NSNumber.class] || + ![height isKindOfClass:NSNumber.class]) { + RCTLogError(@"Invalid width or height given to toDataURL"); + return; + } + NSNumber* w = width; + NSInteger wi = (NSInteger)[w intValue]; + NSNumber* h = height; + NSInteger hi = (NSInteger)[h intValue]; + + CGSize bounds = CGSizeMake(wi, hi); + callback(@[[svg getDataURLwithBounds:bounds]]); + } else { + RCTLogError(@"Invalid svg returned frin registry, expecting RNSVGSvgView, got: %@", view); + } + }]; +} + @end From b4c8985b817cff2e608d0ca90cf291bde09efbb2 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Feb 2019 15:34:10 +0200 Subject: [PATCH 07/71] Sync version used in build.gradle with react-native master Add gradleBuildTools configuration to override gradle classpath --- android/build.gradle | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 141cf705..2b5155e0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,26 +4,22 @@ def safeExtGet(prop, fallback) { buildscript { repositories { - jcenter() google() - maven { - url 'https://maven.google.com/' - name 'Google' - } + jcenter() } dependencies { //noinspection GradleDependency - classpath 'com.android.tools.build:gradle:3.2.1' + classpath rootProject.ext.has('gradleBuildTools') ? rootProject.ext.get('gradleBuildTools') : 'com.android.tools.build:gradle:3.3.0' } } apply plugin: 'com.android.library' android { - compileSdkVersion safeExtGet('compileSdkVersion', 27) + compileSdkVersion safeExtGet('compileSdkVersion', 28) //noinspection GradleDependency - buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3') + buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') defaultConfig { minSdkVersion safeExtGet('minSdkVersion', 16) @@ -37,15 +33,11 @@ android { repositories { mavenLocal() - jcenter() google() + jcenter() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url "$projectDir/../../../node_modules/react-native/android" - } - maven { - url 'https://maven.google.com/' - name 'Google' + url "$rootDir/../node_modules/react-native/android" } } From bc6f46c71389e3edb2b3ff193c143e84255718f2 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 15 Feb 2019 23:19:26 +0200 Subject: [PATCH 08/71] [android] Fix text onPress handling, #941 --- android/src/main/java/com/horcrux/svg/TSpanView.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java index def6d4fd..a9274823 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanView.java +++ b/android/src/main/java/com/horcrux/svg/TSpanView.java @@ -94,17 +94,16 @@ class TSpanView extends TextView { } if (mContent == null) { - mPath = getGroupPath(canvas, paint); - return mPath; + return getGroupPath(canvas, paint); } setupTextPath(); pushGlyphContext(); - mPath = getLinePath(mContent, paint, canvas); + Path path = getLinePath(mContent, paint, canvas); popGlyphContext(); - return mPath; + return path; } double getSubtreeTextChunksTotalAdvance(Paint paint) { @@ -1056,6 +1055,9 @@ class TSpanView extends TextView { if (mRegion == null && mFillPath != null) { mRegion = getRegion(mFillPath); } + if (mRegion == null && mPath != null) { + mRegion = getRegion(mPath); + } if (mStrokeRegion == null && mStrokePath != null) { mStrokeRegion = getRegion(mStrokePath); } From ccb80264622b436bcaebccf05a12d139233e919f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 16 Feb 2019 03:08:01 +0200 Subject: [PATCH 09/71] [android] Fix text onPress handling, improve caching #941 --- .../main/java/com/horcrux/svg/TSpanView.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java index a9274823..2663b153 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanView.java +++ b/android/src/main/java/com/horcrux/svg/TSpanView.java @@ -48,6 +48,7 @@ class TSpanView extends TextView { private static final String OTF = ".otf"; private static final String TTF = ".ttf"; + private Path mCachedPath; @Nullable String mContent; private TextPathView textPath; ArrayList emoji = new ArrayList<>(); @@ -63,6 +64,12 @@ class TSpanView extends TextView { invalidate(); } + @Override + public void invalidate() { + mCachedPath = null; + super.invalidate(); + } + @Override void draw(Canvas canvas, Paint paint, float opacity) { if (mContent != null) { @@ -89,21 +96,22 @@ class TSpanView extends TextView { @Override Path getPath(Canvas canvas, Paint paint) { - if (mPath != null) { - return mPath; + if (mCachedPath != null) { + return mCachedPath; } if (mContent == null) { - return getGroupPath(canvas, paint); + mCachedPath = getGroupPath(canvas, paint); + return mCachedPath; } setupTextPath(); pushGlyphContext(); - Path path = getLinePath(mContent, paint, canvas); + mCachedPath = getLinePath(mContent, paint, canvas); popGlyphContext(); - return path; + return mCachedPath; } double getSubtreeTextChunksTotalAdvance(Paint paint) { From 9b1ccf0e7f5b5d4e4e7dc22082e8fce63cca9be1 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 17 Feb 2019 03:29:59 +0200 Subject: [PATCH 10/71] Implement basic support for native animation of font size --- .../com/horcrux/svg/RenderableViewManager.java | 17 +++++++++++++++++ ios/ViewManagers/RNSVGGroupManager.m | 12 ++++++++++++ ios/ViewManagers/RNSVGTextManager.m | 12 ++++++++++++ lib/extract/extractText.js | 2 +- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 34d31c18..c3858864 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -15,6 +15,7 @@ import android.view.ViewGroup; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.DisplayMetricsHolder; @@ -345,6 +346,22 @@ class RenderableViewManager extends ViewGroupManager { public void setFont(GroupView node, @Nullable ReadableMap font) { node.setFont(font); } + + @ReactProp(name = "fontSize") + public void setFontSize(GroupView node, Dynamic fontSize) { + JavaOnlyMap map = new JavaOnlyMap(); + switch (fontSize.getType()) { + case Number: + map.putDouble("fontSize", fontSize.asDouble()); + break; + case String: + map.putString("fontSize", fontSize.asString()); + break; + default: + return; + } + node.setFont(map); + } } diff --git a/ios/ViewManagers/RNSVGGroupManager.m b/ios/ViewManagers/RNSVGGroupManager.m index ffac110f..8daa2335 100644 --- a/ios/ViewManagers/RNSVGGroupManager.m +++ b/ios/ViewManagers/RNSVGGroupManager.m @@ -21,4 +21,16 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary) +RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGGroup) +{ + if ([json isKindOfClass:[NSString class]]) { + NSString *stringValue = (NSString *)json; + view.font = @{ @"fontSize": stringValue }; + } else { + NSNumber* number = (NSNumber*)json; + double num = [number doubleValue]; + view.font = @{@"fontSize": [NSNumber numberWithDouble:num] }; + } +} + @end diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m index 93d8e536..a273ff08 100644 --- a/ios/ViewManagers/RNSVGTextManager.m +++ b/ios/ViewManagers/RNSVGTextManager.m @@ -68,4 +68,16 @@ RCT_CUSTOM_VIEW_PROPERTY(baselineShift, id, RNSVGText) RCT_EXPORT_VIEW_PROPERTY(lengthAdjust, NSString) RCT_EXPORT_VIEW_PROPERTY(alignmentBaseline, NSString) +RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGGroup) +{ + if ([json isKindOfClass:[NSString class]]) { + NSString *stringValue = (NSString *)json; + view.font = @{ @"fontSize": stringValue }; + } else { + NSNumber* number = (NSNumber*)json; + double num = [number doubleValue]; + view.font = @{@"fontSize": [NSNumber numberWithDouble:num] }; + } +} + @end diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index d908079e..4b0f3d59 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -33,7 +33,7 @@ function parseFontString(font) { const isBold = /bold/.exec(match[1]); const isItalic = /italic/.exec(match[1]); cachedFontObjectsFromString[font] = { - fontSize: match[2] || '12', + fontSize: match[2] || 12, fontWeight: isBold ? 'bold' : 'normal', fontStyle: isItalic ? 'italic' : 'normal', fontFamily: extractSingleFontFamily(match[3]), From d45459cd371e74c0855c4908bfdb433d32d780cc Mon Sep 17 00:00:00 2001 From: Flavio Silva Date: Mon, 18 Feb 2019 17:14:04 -0300 Subject: [PATCH 11/71] [ts] Fix ResponderProps interface: change pointerEvents to have the same type as in ReactNative.ViewProps, i.e., a set of allowed strings, not a function. --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index ec02938e..9e65eb5b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -80,7 +80,7 @@ export interface TouchableProps { } export interface ResponderProps extends ReactNative.GestureResponderHandlers { - pointerEvents?: (event: any) => any, + pointerEvents?: "box-none" | "none" | "box-only" | "auto", } // rgba values inside range 0 to 1 inclusive From 6b3166dc17367bdf5beb7250cbc7b4d78406362b Mon Sep 17 00:00:00 2001 From: Flavio Silva Date: Mon, 18 Feb 2019 17:21:08 -0300 Subject: [PATCH 12/71] [ts] Fix SvgProps interface: make it extends GProps since also accepts all those props. --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 9e65eb5b..696ab79c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -306,7 +306,7 @@ export interface StopProps { } export const Stop: React.ComponentClass; -export interface SvgProps extends ReactNative.ViewProperties { +export interface SvgProps extends GProps, ReactNative.ViewProperties { width?: NumberProp, height?: NumberProp, viewBox?: string, From ffc3c19c0c1d31923ff711bf14545ce67d533729 Mon Sep 17 00:00:00 2001 From: Flavio Silva Date: Mon, 18 Feb 2019 18:00:55 -0300 Subject: [PATCH 13/71] [ts] Fix SvgProps: add color and title. --- index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.d.ts b/index.d.ts index 696ab79c..0dbc14fd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -311,6 +311,8 @@ export interface SvgProps extends GProps, ReactNative.ViewProperties { height?: NumberProp, viewBox?: string, preserveAspectRatio?: string, + color?: int32ARGBColor | rgbaArray | string, + title?: string, } // Svg is both regular and default exported From 01ed35959e3c1a5559f4a5d61f189e6c2328f6b5 Mon Sep 17 00:00:00 2001 From: Flavio Silva Date: Tue, 19 Feb 2019 10:22:59 -0300 Subject: [PATCH 14/71] [docs] Add pencil example to showcase how to cascade color. --- README.md | 29 +++++++++++++++++++++++++---- screenShoots/pencil.png | Bin 0 -> 19533 bytes 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 screenShoots/pencil.png diff --git a/README.md b/README.md index fd513fdc..72236c46 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,15 @@ # NOTICE: -Due to breaking changes in react-native, the version given in the left column +Due to breaking changes in react-native, the version given in the left column (and higher versions) of react-native-svg only supports the react-native version in the right column (and higher versions, if possible). -It is recommended to use the version of react given in the peer dependencies +It is recommended to use the version of react given in the peer dependencies of the react-native version you are using. The latest version of react-native-svg should always work in a clean react-native project. - + | react-native-svg | react-native | |------------------|--------------| | 3.2.0 | 0.29 | @@ -181,7 +181,7 @@ react-native link ``` Make a reproduction of the problem in `App.js` - + ```bash react-native run-ios react-native run-android @@ -395,6 +395,27 @@ originY | 0 | Transform originY coordinates for the current obj ``` +You can cascade colors from the Svg element to its children: + +```html + + + + +``` + +![Pencil](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/pencil.png) + ### Rect The element is used to create a rectangle and variations of a rectangle shape: diff --git a/screenShoots/pencil.png b/screenShoots/pencil.png new file mode 100644 index 0000000000000000000000000000000000000000..7730a1b55a9e60d400c2818038d5ee092e21b797 GIT binary patch literal 19533 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 Ijl2{72jh#{ng9R* literal 0 HcmV?d00001 From 5d1b0eece53af02dbb39cec21304090d7265a1bf Mon Sep 17 00:00:00 2001 From: Flavio Silva Date: Tue, 19 Feb 2019 10:46:38 -0300 Subject: [PATCH 15/71] [docs] Add a code explanation section to the color cascade (pencil) example. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 72236c46..fed84975 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,12 @@ You can cascade colors from the Svg element to its children: ![Pencil](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/pencil.png) + Code explanation: + + * fill prop defines the color inside the object. + * stroke prop defines the color of the line drawn around the object. + * color is a bit special in the sense that it won't color anything by itself, but define a kind of color variable that can be reused by children elements. In this example we're defining a "green" color in the Svg element and using it in the second Path element via stroke="currentColor". The "currentColor" is what refers to that "green" value, and it can be used by other props that accept colors too, e.g. fill="currentColor". + ### Rect The element is used to create a rectangle and variations of a rectangle shape: From 47e2e02bf576a2e2199ed157e4b9e41c99fb2657 Mon Sep 17 00:00:00 2001 From: Flavio Silva Date: Tue, 19 Feb 2019 14:24:34 -0300 Subject: [PATCH 16/71] [docs] Change 'cascade' wording to 'inheritance'. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fed84975..29a43347 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ originY | 0 | Transform originY coordinates for the current obj ``` -You can cascade colors from the Svg element to its children: +Colors set in the Svg element are inherited by its children: ```html Date: Wed, 20 Feb 2019 22:15:46 +0200 Subject: [PATCH 17/71] [android] Make SvgView.drawChildren synchronized Fix race-condition https://github.com/react-native-community/react-native-svg/issues/948 --- android/src/main/java/com/horcrux/svg/SvgView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 4252f703..6709ed62 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -238,7 +238,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC return mCanvas.getClipBounds(); } - void drawChildren(final Canvas canvas) { + synchronized void drawChildren(final Canvas canvas) { mRendered = true; mCanvas = canvas; if (mAlign != null) { From 0b1f53698b84768151dc698aeadb5df9cd501d5c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 21:09:50 +0200 Subject: [PATCH 18/71] [android] Make SvgView.drawChildren synchronized Fix race-condition https://github.com/react-native-community/react-native-svg/issues/948 Refactor toDataUrl --- .../java/com/horcrux/svg/SvgViewModule.java | 26 +++++------ elements/Svg.js | 6 +-- ios/ViewManagers/RNSVGSvgViewManager.m | 44 ++++++++----------- 3 files changed, 29 insertions(+), 47 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java index a7573551..a5cc4927 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java @@ -26,27 +26,21 @@ class SvgViewModule extends ReactContextBaseJavaModule { } - @ReactMethod - public void toDataURL(int tag, Callback successCallback) { - SvgView svg = SvgViewManager.getSvgViewByTag(tag); - - if (svg != null) { - successCallback.invoke(svg.toDataURL()); - } - } - - @ReactMethod public void toDataURL(int tag, ReadableMap options, Callback successCallback) { SvgView svg = SvgViewManager.getSvgViewByTag(tag); if (svg != null) { - successCallback.invoke( - svg.toDataURL( - options.getInt("width"), - options.getInt("height") - ) - ); + if (options != null) { + successCallback.invoke( + svg.toDataURL( + options.getInt("width"), + options.getInt("height") + ) + ); + } else { + successCallback.invoke(svg.toDataURL()); + } } } } diff --git a/elements/Svg.js b/elements/Svg.js index 30f7cbbc..2ed783b8 100644 --- a/elements/Svg.js +++ b/elements/Svg.js @@ -54,11 +54,7 @@ export default class Svg extends Shape { return; } const handle = findNodeHandle(this.root); - if (options) { - RNSVGSvgViewManager.toDataURL(handle, options, callback); - } else { - RNSVGSvgViewManager.toDataURL(handle, callback); - } + RNSVGSvgViewManager.toDataURL(handle, options, callback); }; render() { diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m index 97489a1c..bbd015be 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.m +++ b/ios/ViewManagers/RNSVGSvgViewManager.m @@ -30,39 +30,31 @@ RCT_EXPORT_VIEW_PROPERTY(align, NSString) RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS) RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) -RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - __kindof UIView *view = viewRegistry[reactTag]; - if ([view isKindOfClass:[RNSVGSvgView class]]) { - RNSVGSvgView *svg = view; - callback(@[[svg getDataURL]]); - } else { - RCTLogError(@"Invalid svg returned frin registry, expecting RNSVGSvgView, got: %@", view); - } - }]; -} - RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback) { [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { __kindof UIView *view = viewRegistry[reactTag]; if ([view isKindOfClass:[RNSVGSvgView class]]) { RNSVGSvgView *svg = view; - id width = [options objectForKey:@"width"]; - id height = [options objectForKey:@"height"]; - if (![width isKindOfClass:NSNumber.class] || - ![height isKindOfClass:NSNumber.class]) { - RCTLogError(@"Invalid width or height given to toDataURL"); - return; - } - NSNumber* w = width; - NSInteger wi = (NSInteger)[w intValue]; - NSNumber* h = height; - NSInteger hi = (NSInteger)[h intValue]; + if (options == nil) { + RNSVGSvgView *svg = view; + callback(@[[svg getDataURL]]); + } else { + id width = [options objectForKey:@"width"]; + id height = [options objectForKey:@"height"]; + if (![width isKindOfClass:NSNumber.class] || + ![height isKindOfClass:NSNumber.class]) { + RCTLogError(@"Invalid width or height given to toDataURL"); + return; + } + NSNumber* w = width; + NSInteger wi = (NSInteger)[w intValue]; + NSNumber* h = height; + NSInteger hi = (NSInteger)[h intValue]; - CGSize bounds = CGSizeMake(wi, hi); - callback(@[[svg getDataURLwithBounds:bounds]]); + CGSize bounds = CGSizeMake(wi, hi); + callback(@[[svg getDataURLwithBounds:bounds]]); + } } else { RCTLogError(@"Invalid svg returned frin registry, expecting RNSVGSvgView, got: %@", view); } From 0a282f10733d03183b50576f4a832f4bcaad7885 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 21:10:27 +0200 Subject: [PATCH 19/71] Ignore non-array transforms, align transform handling with react-native --- .../horcrux/svg/RenderableViewManager.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index c3858864..baec315c 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.MatrixMathHelper; @@ -315,8 +316,12 @@ class RenderableViewManager extends ViewGroupManager { float scale = DisplayMetricsHolder.getScreenDisplayMetrics().density; // The following converts the matrix's perspective to a camera distance - // such that the camera perspective looks the same on Android and iOS - float normalizedCameraDistance = scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER; + // such that the camera perspective looks the same on Android and iOS. + // The native Android implementation removed the screen density from the + // calculation, so squaring and a normalization value of + // sqrt(5) produces an exact replica with iOS. + // For more information, see https://github.com/facebook/react-native/pull/18302 + float normalizedCameraDistance = scale * scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER; view.setCameraDistance(normalizedCameraDistance); } @@ -1014,11 +1019,15 @@ class RenderableViewManager extends ViewGroupManager { } @ReactProp(name = "transform") - public void setTransform(VirtualView node, ReadableArray matrix) { - if (matrix == null) { + public void setTransform(VirtualView node, Dynamic matrix) { + if (matrix.getType() != ReadableType.Array) { + return; + } + ReadableArray ma = matrix.asArray(); + if (ma == null) { resetTransformProperty(node); } else { - setTransformProperty(node, matrix); + setTransformProperty(node, ma); } Matrix m = node.getMatrix(); node.mTransform = m; From aec8015255aee4ae148b3a286d02217878db6360 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 21:11:27 +0200 Subject: [PATCH 20/71] Refactor: unroll static loop --- .eslintrc | 2 +- elements/Text.js | 3 +- lib/Matrix2D.js | 5 ++++ lib/extract/extractFill.js | 19 ++++++------ lib/extract/extractResponder.js | 53 +++++++++++++-------------------- lib/extract/extractStroke.js | 40 ++++++++++++++----------- lib/extract/extractTransform.js | 6 +--- 7 files changed, 63 insertions(+), 65 deletions(-) diff --git a/.eslintrc b/.eslintrc index c739b3fc..9fa0ac25 100644 --- a/.eslintrc +++ b/.eslintrc @@ -83,7 +83,7 @@ "curly": 1, // specify curly brace conventions for all control statements "default-case": 0, // require default case in switch statements (off by default) "dot-notation": 1, // encourages use of dot notation whenever possible - "eqeqeq": 1, // require the use of === and !== + "eqeqeq": 0, // require the use of === and !== "guard-for-in": 0, // make sure for-in loops have an if statement (off by default) "no-alert": 0, // disallow the use of alert, confirm, and prompt "no-caller": 1, // disallow use of arguments.caller or arguments.callee diff --git a/elements/Text.js b/elements/Text.js index 043472f7..dcf4821c 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -15,7 +15,8 @@ export default class Text extends Shape { if (matrix) { props.matrix = matrix; } - const text = pickNotNil(extractText(props, true)); + const prop = propsAndStyles(props); + const text = pickNotNil(extractText(prop, true)); this.root.setNativeProps({ ...props, ...text, diff --git a/lib/Matrix2D.js b/lib/Matrix2D.js index 763e607a..63434c70 100644 --- a/lib/Matrix2D.js +++ b/lib/Matrix2D.js @@ -4,6 +4,8 @@ */ const DEG_TO_RAD = Math.PI / 180; +export const identity = [1, 0, 0, 1, 0, 0]; + /** * Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices. * @@ -85,6 +87,9 @@ export default class Matrix2D { * @return {Array} an array with current matrix values. **/ toArray = function() { + if (this.hasInitialState) { + return identity; + } return [this.a, this.b, this.c, this.d, this.tx, this.ty]; }; diff --git a/lib/extract/extractFill.js b/lib/extract/extractFill.js index 0ab4b437..313b88b8 100644 --- a/lib/extract/extractFill.js +++ b/lib/extract/extractFill.js @@ -8,21 +8,22 @@ const fillRules = { nonzero: 1, }; -const fillProps = ['fill', 'fillOpacity', 'fillRule']; -const numFillProps = fillProps.length; - // default fill is black +const black = colorNames.black; const defaultFill = [ 0, - Platform.OS === 'android' ? colorNames.black | 0x0 : colorNames.black, + Platform.OS === 'android' ? black | 0x0 : black, ]; export default function extractFill(props, styleProperties) { - for (let i = 0; i < numFillProps; i++) { - const name = fillProps[i]; - if (props.hasOwnProperty(name)) { - styleProperties.push(name); - } + if (props.fill != null) { + styleProperties.push('fill'); + } + if (props.fillOpacity != null) { + styleProperties.push('fillOpacity'); + } + if (props.fillRule != null) { + styleProperties.push('fillRule'); } const { fill, fillRule, fillOpacity } = props; diff --git a/lib/extract/extractResponder.js b/lib/extract/extractResponder.js index 810b9413..d8b697cf 100644 --- a/lib/extract/extractResponder.js +++ b/lib/extract/extractResponder.js @@ -3,29 +3,21 @@ import { PanResponder } from 'react-native'; const responderProps = Object.keys(PanResponder.create({}).panHandlers); const numResponderProps = responderProps.length; -const touchableProps = [ - 'disabled', - 'onPress', - 'onPressIn', - 'onPressOut', - 'onLongPress', - 'delayPressIn', - 'delayPressOut', - 'delayLongPress', -]; -const numTouchableProps = touchableProps.length; - function hasTouchableProperty(props) { - for (let i = 0; i < numTouchableProps; i++) { - if (props.hasOwnProperty(touchableProps[i])) { - return true; - } - } - return false; + return ( + props.disabled != null || + props.onPress || + props.onPressIn || + props.onPressOut || + props.onLongPress || + props.delayPressIn || + props.delayPressOut || + props.delayLongPress + ); } export default function extractResponder(props, ref) { - const extractedProps = {}; + const o = {}; let responsible = false; for (let i = 0; i < numResponderProps; i++) { @@ -33,31 +25,28 @@ export default function extractResponder(props, ref) { const value = props[key]; if (value) { responsible = true; - extractedProps[key] = value; + o[key] = value; } } const pointerEvents = props.pointerEvents; if (pointerEvents) { - extractedProps.pointerEvents = pointerEvents; + o.pointerEvents = pointerEvents; } if (hasTouchableProperty(props)) { responsible = true; - Object.assign(extractedProps, { - onStartShouldSetResponder: ref.touchableHandleStartShouldSetResponder, - onResponderTerminationRequest: - ref.touchableHandleResponderTerminationRequest, - onResponderGrant: ref.touchableHandleResponderGrant, - onResponderMove: ref.touchableHandleResponderMove, - onResponderRelease: ref.touchableHandleResponderRelease, - onResponderTerminate: ref.touchableHandleResponderTerminate, - }); + o.onResponderMove = ref.touchableHandleResponderMove; + o.onResponderGrant = ref.touchableHandleResponderGrant; + o.onResponderRelease = ref.touchableHandleResponderRelease; + o.onResponderTerminate = ref.touchableHandleResponderTerminate; + o.onStartShouldSetResponder = ref.touchableHandleStartShouldSetResponder; + o.onResponderTerminationRequest = ref.touchableHandleResponderTerminationRequest; } if (responsible) { - extractedProps.responsible = true; + o.responsible = true; } - return extractedProps; + return o; } diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index ec8fa516..df04204b 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -14,24 +14,30 @@ const joins = { round: 1, }; -const strokeProps = [ - 'stroke', - 'strokeWidth', - 'strokeOpacity', - 'strokeDasharray', - 'strokeDashoffset', - 'strokeLinecap', - 'strokeLinejoin', - 'strokeMiterlimit', -]; -const numStrokeProps = strokeProps.length; - export default function extractStroke(props, styleProperties) { - for (let i = 0; i < numStrokeProps; i++) { - const name = strokeProps[i]; - if (props.hasOwnProperty(name)) { - styleProperties.push(name); - } + if (props.stroke != null) { + styleProperties.push('stroke'); + } + if (props.strokeWidth != null) { + styleProperties.push('strokeWidth'); + } + if (props.strokeOpacity != null) { + styleProperties.push('strokeOpacity'); + } + if (props.strokeDasharray != null) { + styleProperties.push('strokeDasharray'); + } + if (props.strokeDashoffset != null) { + styleProperties.push('strokeDashoffset'); + } + if (props.strokeLinecap != null) { + styleProperties.push('strokeLinecap'); + } + if (props.strokeLinejoin != null) { + styleProperties.push('strokeLinejoin'); + } + if (props.strokeMiterlimit != null) { + styleProperties.push('strokeMiterlimit'); } const { stroke, strokeWidth = 1, strokeDasharray } = props; diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index 94a76472..e1fc9dc6 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -1,4 +1,4 @@ -import Matrix2D from '../Matrix2D'; +import Matrix2D, { identity } from '../Matrix2D'; import transformParser from './transform'; const pooledMatrix = new Matrix2D(); @@ -51,9 +51,7 @@ export function props2transform(props) { const skew = universal2axis(props.skew, props.skewX, props.skewY); const translate = universal2axis( props.translate, - // eslint-disable-next-line eqeqeq props.translateX == null ? props.x || 0 : props.translateX, - // eslint-disable-next-line eqeqeq props.translateY == null ? props.y || 0 : props.translateY, ); @@ -102,8 +100,6 @@ export function transformToMatrix(props, transform) { return pooledMatrix.toArray(); } -const identity = [1, 0, 0, 1, 0, 0]; - export default function extractTransform(props) { if (Array.isArray(props)) { return props; From ffb04c84143c506a6f5cde8bb97ff9fab09d1595 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 21:16:20 +0200 Subject: [PATCH 21/71] [android] Implement vectorEffect nonScalingStroke / non-scaling-stroke --- .../java/com/horcrux/svg/RenderableView.java | 21 +++++++++++++++++++ .../horcrux/svg/RenderableViewManager.java | 6 ++++++ lib/extract/extractStroke.js | 9 ++++++++ 3 files changed, 36 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java index d0f54d4b..84c29d08 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableView.java +++ b/android/src/main/java/com/horcrux/svg/RenderableView.java @@ -56,6 +56,14 @@ abstract public class RenderableView extends VirtualView { private static final int FILL_RULE_EVENODD = 0; static final int FILL_RULE_NONZERO = 1; + // vectorEffect + static final int VECTOR_EFFECT_DEFAULT = 0; + static final int VECTOR_EFFECT_NON_SCALING_STROKE = 1; + static final int VECTOR_EFFECT_INHERIT = 2; + static final int VECTOR_EFFECT_URI = 3; + + public int vectorEffect = VECTOR_EFFECT_DEFAULT; + /* Used in mergeProperties, keep public */ @@ -86,6 +94,12 @@ abstract public class RenderableView extends VirtualView { private static final Pattern regex = Pattern.compile("[0-9.-]+"); + @ReactProp(name = "vectorEffect", defaultInt = VECTOR_EFFECT_DEFAULT) + public void setVectorEffect(int vectorEffect) { + this.vectorEffect = vectorEffect; + invalidate(); + } + @ReactProp(name = "fill") public void setFill(@Nullable Dynamic fill) { if (fill == null || fill.isNull()) { @@ -322,7 +336,14 @@ abstract public class RenderableView extends VirtualView { mPath = getPath(canvas, paint); mPath.setFillType(fillRule); } + boolean nonScalingStroke = vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE; Path path = mPath; + if (nonScalingStroke) { + Path scaled = new Path(); + mPath.transform(canvas.getMatrix(), scaled); + canvas.setMatrix(null); + path = scaled; + } RectF clientRect = new RectF(); path.computeBounds(clientRect, true); diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index baec315c..f6bf0e36 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -45,6 +45,7 @@ import static com.facebook.react.uimanager.ViewProps.*; import static com.horcrux.svg.RenderableView.CAP_ROUND; import static com.horcrux.svg.RenderableView.FILL_RULE_NONZERO; import static com.horcrux.svg.RenderableView.JOIN_ROUND; +import static com.horcrux.svg.RenderableView.VECTOR_EFFECT_DEFAULT; /** * ViewManager for all RNSVG views @@ -1013,6 +1014,11 @@ class RenderableViewManager extends ViewGroupManager { node.setStrokeLinejoin(strokeLinejoin); } + @ReactProp(name = "vectorEffect", defaultInt = VECTOR_EFFECT_DEFAULT) + public void setVectorEffect(RenderableView node, int vectorEffect) { + node.setVectorEffect(vectorEffect); + } + @ReactProp(name = "matrix") public void setMatrix(VirtualView node, Dynamic matrixArray) { node.setMatrix(matrixArray); diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index df04204b..a9b8cc0e 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -14,6 +14,14 @@ const joins = { round: 1, }; +const vectorEffects = { + none: 0, + default: 0, + nonScalingStroke: 1, + 'non-scaling-stroke': 1, + inherit: 2, +}; + export default function extractStroke(props, styleProperties) { if (props.stroke != null) { styleProperties.push('stroke'); @@ -58,5 +66,6 @@ export default function extractStroke(props, styleProperties) { strokeWidth, strokeDashoffset: strokeDasharray ? +props.strokeDashoffset || 0 : null, strokeMiterlimit: parseFloat(props.strokeMiterlimit) || 4, + vectorEffect: vectorEffects[props.vectorEffect] || 0, }; } From 1b050834d5c4a8410054bbdbe18470c561325349 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 22:08:10 +0200 Subject: [PATCH 22/71] [ios] Implement vectorEffect nonScalingStroke / non-scaling-stroke --- ios/RNSVGRenderable.h | 2 ++ ios/RNSVGRenderable.m | 15 +++++++++++++++ ios/ViewManagers/RNSVGRenderableManager.m | 1 + lib/extract/extractStroke.js | 1 + 4 files changed, 19 insertions(+) diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index d59d48ad..2973f25b 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -12,6 +12,7 @@ #import "RNSVGCGFCRule.h" #import "RNSVGNode.h" #import "RNSVGLength.h" +#import "RNSVGVectorEffect.h" #import "RNSVGPercentageConverter.h" @interface RNSVGRenderable : RNSVGNode @@ -27,6 +28,7 @@ @property (nonatomic, assign) CGFloat strokeMiterlimit; @property (nonatomic, strong) NSArray *strokeDasharray; @property (nonatomic, assign) CGFloat strokeDashoffset; +@property (nonatomic, assign) RNSVGVectorEffect vectorEffect; @property (nonatomic, copy) NSArray *propList; - (void)setHitArea:(CGPathRef)path; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index d8067aae..d9a898b2 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -10,6 +10,7 @@ #import "RNSVGClipPath.h" #import "RNSVGMask.h" #import "RNSVGViewBox.h" +#import "RNSVGVectorEffect.h" @implementation RNSVGRenderable { @@ -143,6 +144,15 @@ _strokeDashoffset = strokeDashoffset; } +- (void)setVectorEffect:(RNSVGVectorEffect)vectorEffect +{ + if (vectorEffect == _vectorEffect) { + return; + } + [self invalidate]; + _vectorEffect = vectorEffect; +} + - (void)setPropList:(NSArray *)propList { if (propList == _propList) { @@ -305,6 +315,11 @@ UInt32 saturate(CGFloat value) { self.clientRect = clientRect; + if (_vectorEffect == kRNSVGVectorEffectNonScalingStroke) { + path = CGPathCreateCopyByTransformingPath(path, &svgToClientTransform); + CGContextConcatCTM(context, CGAffineTransformInvert(svgToClientTransform)); + } + CGAffineTransform vbmatrix = self.svgView.getViewBoxTransform; CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms); CGAffineTransform matrix = CGAffineTransformConcat(transform, vbmatrix); diff --git a/ios/ViewManagers/RNSVGRenderableManager.m b/ios/ViewManagers/RNSVGRenderableManager.m index befdd937..fae93de7 100644 --- a/ios/ViewManagers/RNSVGRenderableManager.m +++ b/ios/ViewManagers/RNSVGRenderableManager.m @@ -31,6 +31,7 @@ RCT_EXPORT_VIEW_PROPERTY(strokeLinejoin, CGLineJoin) RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, NSArray) RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat) RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(vectorEffect, int) RCT_EXPORT_VIEW_PROPERTY(propList, NSArray) @end diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index a9b8cc0e..27be678a 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -20,6 +20,7 @@ const vectorEffects = { nonScalingStroke: 1, 'non-scaling-stroke': 1, inherit: 2, + uri: 3 }; export default function extractStroke(props, styleProperties) { From 014e2375fa4b73feb98cea3a4d69700c4df32514 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 22:42:53 +0200 Subject: [PATCH 23/71] [ios] Implement vectorEffect nonScalingStroke / non-scaling-stroke Add missing header file --- ios/Utils/RNSVGVectorEffect.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ios/Utils/RNSVGVectorEffect.h diff --git a/ios/Utils/RNSVGVectorEffect.h b/ios/Utils/RNSVGVectorEffect.h new file mode 100644 index 00000000..0ab17036 --- /dev/null +++ b/ios/Utils/RNSVGVectorEffect.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, react-native-community. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +typedef CF_ENUM(int32_t, RNSVGVectorEffect) { + kRNSVGVectorEffectDefault, + kRNSVGVectorEffectNonScalingStroke, + kRNSVGVectorEffectInherit, + kRNSVGVectorEffectUri +}; From d80ab86c185b62b12af07f9e93f546fd08e88bd9 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 23:06:53 +0200 Subject: [PATCH 24/71] Refactor and simplify color processing --- lib/extract/extractBrush.js | 5 ++--- lib/extract/extractColor.js | 21 ++++++++++++++------- lib/extract/extractFill.js | 8 ++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/extract/extractBrush.js b/lib/extract/extractBrush.js index 882fadfe..38e8f12b 100644 --- a/lib/extract/extractBrush.js +++ b/lib/extract/extractBrush.js @@ -1,5 +1,4 @@ -import extractColor from './extractColor'; -import { Platform } from 'react-native'; +import extractColor, { integerColor } from './extractColor'; const urlIdPattern = /^url\(#(.+)\)$/; @@ -8,7 +7,7 @@ const currentColorBrush = [2]; export default function extractBrush(color) { if (typeof color === 'number') { if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { - return [0, Platform.OS === 'android' ? color | 0x0 : color]; + return [0, integerColor(color)]; } } diff --git a/lib/extract/extractColor.js b/lib/extract/extractColor.js index f2539279..9b1167b9 100644 --- a/lib/extract/extractColor.js +++ b/lib/extract/extractColor.js @@ -345,7 +345,7 @@ function rgbFromString(string) { return null; } - return Platform.OS === 'android' ? rgb | 0x0 : rgb; + return integerColor(rgb); } else { return null; } @@ -403,11 +403,22 @@ function colorFromString(string) { } } +const identity = x => x; + +const toSignedInt32 = x => x | 0x0; + +// Android use 32 bit *signed* integer to represent the color +// We utilize the fact that bitwise operations in JS also operates on +// signed 32 bit integers, so that we can use those to convert from +// *unsigned* to *signed* 32bit in that way. +export const integerColor = + Platform.OS === 'android' ? toSignedInt32 : identity; + // Returns 0xaarrggbb or null export default function extractColor(color) { if (typeof color === 'number') { if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { - return Platform.OS === 'android' ? color | 0x0 : color; + return integerColor(color); } return null; } @@ -430,9 +441,5 @@ export default function extractColor(color) { Math.round(b * 255)) >>> 0; - // Android use 32 bit *signed* integer to represent the color - // We utilize the fact that bitwise operations in JS also operates on - // signed 32 bit integers, so that we can use those to convert from - // *unsigned* to *signed* 32bit int that way. - return Platform.OS === 'android' ? int32Color | 0x0 : int32Color; + return integerColor(int32Color); } diff --git a/lib/extract/extractFill.js b/lib/extract/extractFill.js index 313b88b8..18ec9e40 100644 --- a/lib/extract/extractFill.js +++ b/lib/extract/extractFill.js @@ -1,7 +1,6 @@ import extractBrush from './extractBrush'; import extractOpacity from './extractOpacity'; -import { colorNames } from './extractColor'; -import { Platform } from 'react-native'; +import { colorNames, integerColor } from './extractColor'; const fillRules = { evenodd: 0, @@ -10,10 +9,7 @@ const fillRules = { // default fill is black const black = colorNames.black; -const defaultFill = [ - 0, - Platform.OS === 'android' ? black | 0x0 : black, -]; +const defaultFill = [0, integerColor(black)]; export default function extractFill(props, styleProperties) { if (props.fill != null) { From dc9470700890056df872119e5dd4aab614732014 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 27 Feb 2019 23:23:09 +0200 Subject: [PATCH 25/71] Refactor props handling in text, tspan and clippath --- elements/ClipPath.js | 5 +++-- elements/TSpan.js | 8 ++++---- elements/Text.js | 5 ++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/elements/ClipPath.js b/elements/ClipPath.js index 1c3c91b7..15a1807f 100644 --- a/elements/ClipPath.js +++ b/elements/ClipPath.js @@ -7,12 +7,13 @@ export default class ClipPath extends Shape { static displayName = 'ClipPath'; render() { - const { id, children } = this.props; + const { props } = this; + const { id, children } = props; return ( {children} diff --git a/elements/TSpan.js b/elements/TSpan.js index 2362026d..18524fde 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -14,16 +14,16 @@ export default class TSpan extends Shape { if (matrix) { props.matrix = matrix; } - const text = pickNotNil(extractText(props, false)); + const prop = propsAndStyles(props); + const text = pickNotNil(extractText(prop, false)); this.root.setNativeProps({ - ...props, + ...prop, ...text, }); }; render() { - const props = this.props; - const prop = propsAndStyles(props); + const prop = propsAndStyles(this.props); return ( Date: Wed, 27 Feb 2019 14:46:58 -0700 Subject: [PATCH 26/71] Update typings for vector effects --- index.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index ec02938e..f0eb218f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -101,6 +101,10 @@ export interface ClipProps { clipRule?: FillRule, clipPath?: string } + +VectorEffectProps { + vectorEffect?: "none" | "non-scaling-stroke"; +} export interface DefinitionProps { id?: string, @@ -180,7 +184,7 @@ export interface CommonMaskProps { mask?: string; } -export interface CommonPathProps extends FillProps, StrokeProps, ClipProps, TransformProps, ResponderProps, TouchableProps, DefinitionProps, CommonMaskProps {} +export interface CommonPathProps extends FillProps, StrokeProps, ClipProps, TransformProps, VectorEffectProps, ResponderProps, TouchableProps, DefinitionProps, CommonMaskProps {} // Element props export interface CircleProps extends CommonPathProps { From af35acc008c83ae24b52110d4d7be76d54a953d1 Mon Sep 17 00:00:00 2001 From: scottmas Date: Wed, 27 Feb 2019 16:22:11 -0700 Subject: [PATCH 27/71] Tweak typing --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index f0eb218f..9c81a6fa 100644 --- a/index.d.ts +++ b/index.d.ts @@ -103,7 +103,7 @@ export interface ClipProps { } VectorEffectProps { - vectorEffect?: "none" | "non-scaling-stroke"; + vectorEffect?: "none" | "non-scaling-stroke" | "nonScalingStroke" | "default" | "inherit" | "uri"; } export interface DefinitionProps { From 613f801fa267d9e0cb24da3ac088aa344691fb00 Mon Sep 17 00:00:00 2001 From: krister Date: Fri, 1 Mar 2019 18:51:01 +0200 Subject: [PATCH 28/71] Update react-native-svg-transformer documentation - Use `metro.config.js` instead of `rn-cli.config.js`. - React Native 0.59.x ships with `metro.config.js` file, so it will be used as the default config for Metro. - `metro.config.js` file works on React Native 0.57 and 0.58 too. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd513fdc..9fe95d68 100644 --- a/README.md +++ b/README.md @@ -291,9 +291,10 @@ export default () => ( Try [react-native-svg-transformer](https://github.com/kristerkari/react-native-svg-transformer) to get compile time conversion and cached transformations. https://github.com/kristerkari/react-native-svg-transformer#installation-and-configuration -https://github.com/kristerkari/react-native-svg-transformer#for-react-native-v057-or-newer +https://github.com/kristerkari/react-native-svg-transformer#for-react-native-v057-or-newer--expo-sdk-v3100-or-newer + +`metro.config.js` -rn-cli.config.js ```js const { getDefaultConfig } = require("metro-config"); From 742b79b9b9849227d43bfd3d68a5f1b54fdb1d20 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Mar 2019 04:23:39 +0200 Subject: [PATCH 29/71] Add refactored implementation from https://github.com/msand/swgs/ --- index.web.js | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 2 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 index.web.js diff --git a/index.web.js b/index.web.js new file mode 100644 index 00000000..8a716918 --- /dev/null +++ b/index.web.js @@ -0,0 +1,407 @@ +import { createElement } from 'react-native-web'; + +/** + * The `react-native-svg` has some non-standard api's that do not match with the + * properties that can be applied to web SVG elements. This prepare function removes + * those properties and adds the properties back in their correct location. + * + * @param {Object} props Properties given to us. + * @returns {Object} Cleaned object. + * @private + */ +function prepare(props) { + /* eslint-disable no-unused-vars */ + const { + translate, + scale, + rotate, + skewX, + skewY, + originX, + originY, + fontFamily, + fontSize, + fontWeight, + fontStyle, + style: ignoredStyles, + ...clean + } = props; + /* eslint-enable no-unused-vars */ + + const transform = []; + + // + // Correctly apply the transformation properties. + // To apply originX and originY we need to translate the element on those values and + // translate them back once the element is scaled, rotated and skewed. + // + if ('originX' in props || 'originY' in props) { + transform.push(`translate(${props.originX || 0}, ${props.originY || 0})`); + } + if ('translate' in props) { + transform.push(`translate(${props.translate})`); + } + if ('scale' in props) { + transform.push(`scale(${props.scale})`); + } + if ('rotate' in props) { + transform.push(`rotate(${props.rotate})`); + } + if ('skewX' in props) { + transform.push(`skewX(${props.skewX})`); + } + if ('skewY' in props) { + transform.push(`skewY(${props.skewY})`); + } + if ('originX' in props || 'originY' in props) { + transform.push(`translate(${-props.originX || 0}, ${-props.originY || 0})`); + } + if (transform.length) { + clean.transform = transform.join(' '); + } + + // + // Correctly set the initial style value. + // + const style = 'style' in props ? props.style : {}; + + // + // This is the nasty part where we depend on React internals to work as + // intended. If we add an empty object as style, it shouldn't render a `style` + // attribute. So we can safely conditionally add things to our `style` object + // and re-introduce it to our `clean` object + // + if ('fontFamily' in props) { + style.fontFamily = props.fontFamily; + } + if ('fontSize' in props) { + style.fontSize = props.fontSize; + } + if ('fontWeight' in props) { + style.fontWeight = props.fontWeight; + } + if ('fontStyle' in props) { + style.fontStyle = props.fontStyle; + } + clean.style = style; + + // + // React-Native svg provides as a default of `xMidYMid` if aspectRatio is not + // specified with align information. So we need to support this behavior and + // correctly default to `xMidYMid [mode]`. + // + const preserve = clean.preserveAspectRatio; + if (preserve && preserve !== 'none' && !~preserve.indexOf(' ')) { + clean.preserveAspectRatio = 'xMidYMid ' + preserve; + } + + return clean; +} + +/** + * Return a circle SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Circle SVG. + * @public + */ +function Circle(props) { + return createElement('circle', prepare(props)); +} + +/** + * Return a clipPath SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} ClipPath SVG. + * @public + */ +function ClipPath(props) { + return createElement('clipPath', prepare(props)); +} + +/** + * Return a defs SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Defs SVG. + * @public + */ +function Defs(props) { + return createElement('defs', prepare(props)); +} + +/** + * Return a ellipse SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Ellipse SVG. + * @public + */ +function Ellipse(props) { + return createElement('ellipse', prepare(props)); +} + +/** + * Return a g SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} G SVG. + * @public + */ +function G(props) { + const { x, y, ...rest } = props; + + if ((x || y) && !rest.translate) { + rest.translate = `${x || 0}, ${y || 0}`; + } + + return createElement('g', prepare(rest)); +} + +/** + * Return a image SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Image SVG. + * @public + */ +function Image(props) { + return createElement('image', prepare(props)); +} + +/** + * Return a line SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Line SVG. + * @public + */ +function Line(props) { + return createElement('line', prepare(props)); +} + +/** + * Return a linearGradient SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} LinearGradient SVG. + * @public + */ +function LinearGradient(props) { + return createElement('linearGradient', prepare(props)); +} + +/** + * Return a path SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Path SVG. + * @public + */ +function Path(props) { + return createElement('path', prepare(props)); +} + +/** + * Return a polygon SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Polygon SVG. + * @public + */ +function Polygon(props) { + return createElement('polygon', prepare(props)); +} + +/** + * Return a polyline SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Polyline SVG. + * @public + */ +function Polyline(props) { + return createElement('polyline', prepare(props)); +} + +/** + * Return a radialGradient SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} RadialGradient SVG. + * @public + */ +function RadialGradient(props) { + return createElement('radialGradient', prepare(props)); +} + +/** + * Return a rect SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Rect SVG. + * @public + */ +function Rect(props) { + return createElement('rect', prepare(props)); +} + +/** + * Return a stop SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Stop SVG. + * @public + */ +function Stop(props) { + return createElement('stop', prepare(props)); +} + +/** + * Return a SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} SVG. + * @public + */ +function Svg(props) { + const { title, ...rest } = props; + + if (title) { + return createElement( + 'svg', + { role: 'img', 'aria-label': '[title]', ...prepare(rest) }, + [createElement('title', {}, title), props.children], + ); + } + + return createElement('svg', prepare(rest)); +} + +/** + * Return a symbol SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Symbol SVG. + * @public + */ +function Symbol(props) { + return createElement('symbol', prepare(props)); +} + +/** + * Return a text SVG element. + * + * @returns {React.Component} Text SVG. + * @public + * @param {Object} props The properties that are spread on the SVG element. + * @param {String} props.x x position + * @param {String} props.y y position + * @param {String} props.dx delta x + * @param {String} props.dy delta y + * @param {String} props.rotate rotation + */ +function Text(props) { + const { x, y, dx, dy, rotate, ...rest } = props; + + return createElement('text', { + ...prepare(rest), + ...{ x, y, dx, dy, rotate }, + }); +} + +/** + * Return a tspan SVG element. + * + * @returns {React.Component} TSpan SVG. + * @public + * @param {Object} props The properties that are spread on the SVG element. + * @param {String} props.x x position + * @param {String} props.y y position + * @param {String} props.dx delta x + * @param {String} props.dy delta y + * @param {String} props.rotate rotation + */ +function TSpan(props) { + const { x, y, dx, dy, rotate, ...rest } = props; + + return createElement('tspan', { + ...prepare(rest), + ...{ x, y, dx, dy, rotate }, + }); +} + +/** + * Return a textpath SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} TextPath SVG. + * @public + */ +function TextPath(props) { + return createElement('textPath', prepare(props)); +} + +/** + * Return a use SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Use SVG. + * @public + */ +function Use(props) { + return createElement('use', prepare(props)); +} + +/** + * Return a mask SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Use SVG. + * @public + */ +function Mask(props) { + return createElement('mask', prepare(props)); +} + +/** + * Return a pattern SVG element. + * + * @param {Object} props The properties that are spread on the SVG element. + * @returns {React.Component} Use SVG. + * @public + */ +function Pattern(props) { + return createElement('pattern', prepare(props)); +} + +// +// Expose everything in the same way as `react-native-svg` is doing. +// +export { + Circle, + ClipPath, + Defs, + Ellipse, + G, + Image, + Line, + LinearGradient, + Mask, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + Stop, + Svg, + Symbol, + TSpan, + Text, + TextPath, + Use, +}; + +export default Svg; diff --git a/package.json b/package.json index d0db0fd7..4fce0bf0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/react-native-community/react-native-svg" }, "license": "MIT", - "main": "./index.js", + "main": "./index", "keywords": [ "react-component", "react-native", @@ -20,7 +20,7 @@ ], "scripts": { "lint": "eslint ./", - "format": "prettier index.js './{elements,lib}/*.js' './lib/extract/e*.js' --write", + "format": "prettier index.js index.web.js './{elements,lib}/*.js' './lib/extract/e*.js' --write", "peg": "pegjs -o ./lib/extract/transform.js ./lib/extract/transform.peg" }, "peerDependencies": { From 03e10ef038424415998daca47cfcba010bcef8cb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Mar 2019 04:43:35 +0200 Subject: [PATCH 30/71] Refactor web implementation --- index.web.js | 74 ++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/index.web.js b/index.web.js index 8a716918..e3af514d 100644 --- a/index.web.js +++ b/index.web.js @@ -10,7 +10,6 @@ import { createElement } from 'react-native-web'; * @private */ function prepare(props) { - /* eslint-disable no-unused-vars */ const { translate, scale, @@ -23,73 +22,58 @@ function prepare(props) { fontSize, fontWeight, fontStyle, - style: ignoredStyles, + style = {}, ...clean } = props; - /* eslint-enable no-unused-vars */ - const transform = []; - - // // Correctly apply the transformation properties. // To apply originX and originY we need to translate the element on those values and // translate them back once the element is scaled, rotated and skewed. - // - if ('originX' in props || 'originY' in props) { - transform.push(`translate(${props.originX || 0}, ${props.originY || 0})`); + const transform = []; + + /* eslint-disable eqeqeq */ + if (originX != null || originY != null) { + transform.push(`translate(${originX || 0}, ${originY || 0})`); } - if ('translate' in props) { - transform.push(`translate(${props.translate})`); + if (translate != null) { + transform.push(`translate(${translate})`); } - if ('scale' in props) { - transform.push(`scale(${props.scale})`); + if (scale != null) { + transform.push(`scale(${scale})`); } - if ('rotate' in props) { - transform.push(`rotate(${props.rotate})`); + if (rotate != null) { + transform.push(`rotate(${rotate})`); } - if ('skewX' in props) { - transform.push(`skewX(${props.skewX})`); + if (skewX != null) { + transform.push(`skewX(${skewX})`); } - if ('skewY' in props) { - transform.push(`skewY(${props.skewY})`); + if (skewY != null) { + transform.push(`skewY(${skewY})`); } - if ('originX' in props || 'originY' in props) { - transform.push(`translate(${-props.originX || 0}, ${-props.originY || 0})`); + if (originX != null || originY != null) { + transform.push(`translate(${-originX || 0}, ${-originY || 0})`); } + if (transform.length) { clean.transform = transform.join(' '); } - // - // Correctly set the initial style value. - // - const style = 'style' in props ? props.style : {}; - - // - // This is the nasty part where we depend on React internals to work as - // intended. If we add an empty object as style, it shouldn't render a `style` - // attribute. So we can safely conditionally add things to our `style` object - // and re-introduce it to our `clean` object - // - if ('fontFamily' in props) { - style.fontFamily = props.fontFamily; + if (fontFamily != null) { + style.fontFamily = fontFamily; } - if ('fontSize' in props) { - style.fontSize = props.fontSize; + if (fontSize != null) { + style.fontSize = fontSize; } - if ('fontWeight' in props) { - style.fontWeight = props.fontWeight; + if (fontWeight != null) { + style.fontWeight = fontWeight; } - if ('fontStyle' in props) { - style.fontStyle = props.fontStyle; + if (fontStyle != null) { + style.fontStyle = fontStyle; } + /* eslint-enable eqeqeq */ clean.style = style; - // - // React-Native svg provides as a default of `xMidYMid` if aspectRatio is not - // specified with align information. So we need to support this behavior and - // correctly default to `xMidYMid [mode]`. - // + // We provide a default of `xMidYMid` if aspectRatio is not specified with align information. const preserve = clean.preserveAspectRatio; if (preserve && preserve !== 'none' && !~preserve.indexOf(' ')) { clean.preserveAspectRatio = 'xMidYMid ' + preserve; From 5f34b210a150f403004ab519806ec75f98ddc29a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Mar 2019 04:50:04 +0200 Subject: [PATCH 31/71] Refactor web implementation --- index.web.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/index.web.js b/index.web.js index e3af514d..b4cf8cee 100644 --- a/index.web.js +++ b/index.web.js @@ -287,12 +287,7 @@ function Symbol(props) { * @param {String} props.rotate rotation */ function Text(props) { - const { x, y, dx, dy, rotate, ...rest } = props; - - return createElement('text', { - ...prepare(rest), - ...{ x, y, dx, dy, rotate }, - }); + return createElement('text', prepare(props)); } /** @@ -308,12 +303,7 @@ function Text(props) { * @param {String} props.rotate rotation */ function TSpan(props) { - const { x, y, dx, dy, rotate, ...rest } = props; - - return createElement('tspan', { - ...prepare(rest), - ...{ x, y, dx, dy, rotate }, - }); + return createElement('tspan', prepare(props)); } /** From 21c236abef89bda7f77ae015efc5d9aade31984f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Mar 2019 05:47:09 +0200 Subject: [PATCH 32/71] Fix handling of style arrays and react-native-web style numbers --- index.web.js | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/index.web.js b/index.web.js index b4cf8cee..1e47ecab 100644 --- a/index.web.js +++ b/index.web.js @@ -1,4 +1,18 @@ -import { createElement } from 'react-native-web'; +import { createElement, StyleSheet } from 'react-native-web'; + +function resolve(style1, style2) { + if (style1 && style2) { + return StyleSheet + ? [style1, style2] + : { + // Compatibility for arrays of styles in plain react web + ...(style1.length ? Object.assign({}, ...style1) : style1), + ...style2, + }; + } else { + return style1 || style2; + } +} /** * The `react-native-svg` has some non-standard api's that do not match with the @@ -22,7 +36,7 @@ function prepare(props) { fontSize, fontWeight, fontStyle, - style = {}, + style, ...clean } = props; @@ -58,20 +72,24 @@ function prepare(props) { clean.transform = transform.join(' '); } + const styles = {}; if (fontFamily != null) { - style.fontFamily = fontFamily; + styles.fontFamily = fontFamily; } if (fontSize != null) { - style.fontSize = fontSize; + styles.fontSize = fontSize; } if (fontWeight != null) { - style.fontWeight = fontWeight; + styles.fontWeight = fontWeight; } if (fontStyle != null) { - style.fontStyle = fontStyle; + styles.fontStyle = fontStyle; } /* eslint-enable eqeqeq */ - clean.style = style; + clean.style = resolve( + style, + styles, + ); // We provide a default of `xMidYMid` if aspectRatio is not specified with align information. const preserve = clean.preserveAspectRatio; From f9c9ac8920614ea62e8c03ae808292228484345b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Mar 2019 05:55:55 +0200 Subject: [PATCH 33/71] Refactor web implementation --- index.web.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.web.js b/index.web.js index 1e47ecab..e42cd648 100644 --- a/index.web.js +++ b/index.web.js @@ -1,16 +1,16 @@ import { createElement, StyleSheet } from 'react-native-web'; -function resolve(style1, style2) { - if (style1 && style2) { +function resolve(styleProp, cleanedProps) { + if (styleProp) { return StyleSheet - ? [style1, style2] + ? [styleProp, cleanedProps] : { // Compatibility for arrays of styles in plain react web - ...(style1.length ? Object.assign({}, ...style1) : style1), - ...style2, + ...(styleProp.length ? Object.assign({}, ...styleProp) : styleProp), + ...cleanedProps, }; } else { - return style1 || style2; + return cleanedProps; } } From 53e91fcdfc94ca892352ee5412a14f8f117e3bf8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Mar 2019 06:28:25 +0200 Subject: [PATCH 34/71] [web] Remove incorrect preserveAspectRatio logic --- index.web.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/index.web.js b/index.web.js index e42cd648..1e33898c 100644 --- a/index.web.js +++ b/index.web.js @@ -91,12 +91,6 @@ function prepare(props) { styles, ); - // We provide a default of `xMidYMid` if aspectRatio is not specified with align information. - const preserve = clean.preserveAspectRatio; - if (preserve && preserve !== 'none' && !~preserve.indexOf(' ')) { - clean.preserveAspectRatio = 'xMidYMid ' + preserve; - } - return clean; } From 70c5954285da456f83a627851a53996dfe14bece Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Mar 2019 01:39:05 +0200 Subject: [PATCH 35/71] [web] Fix rotation transform handling, and simplify style resolution --- index.web.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/index.web.js b/index.web.js index 1e33898c..663eb33f 100644 --- a/index.web.js +++ b/index.web.js @@ -4,11 +4,10 @@ function resolve(styleProp, cleanedProps) { if (styleProp) { return StyleSheet ? [styleProp, cleanedProps] - : { - // Compatibility for arrays of styles in plain react web - ...(styleProp.length ? Object.assign({}, ...styleProp) : styleProp), - ...cleanedProps, - }; + : // Compatibility for arrays of styles in plain react web + styleProp.length + ? Object.assign({}, ...styleProp, cleanedProps) + : Object.assign({}, styleProp, cleanedProps); } else { return cleanedProps; } @@ -27,7 +26,7 @@ function prepare(props) { const { translate, scale, - rotate, + rotation, skewX, skewY, originX, @@ -55,8 +54,9 @@ function prepare(props) { if (scale != null) { transform.push(`scale(${scale})`); } - if (rotate != null) { - transform.push(`rotate(${rotate})`); + // rotation maps to rotate, not to collide with the text rotate attribute + if (rotation != null) { + transform.push(`rotate(${rotation})`); } if (skewX != null) { transform.push(`skewX(${skewX})`); @@ -86,10 +86,7 @@ function prepare(props) { styles.fontStyle = fontStyle; } /* eslint-enable eqeqeq */ - clean.style = resolve( - style, - styles, - ); + clean.style = resolve(style, styles); return clean; } From 7b830738566d95e6f49d3f2e1ebf0fa438dd830c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Mar 2019 02:33:53 +0200 Subject: [PATCH 36/71] [web] Refactor and cleanup --- index.web.js | 195 +------------------------------------------------ lib/resolve.js | 15 ++++ 2 files changed, 18 insertions(+), 192 deletions(-) create mode 100644 lib/resolve.js diff --git a/index.web.js b/index.web.js index 663eb33f..764c66bf 100644 --- a/index.web.js +++ b/index.web.js @@ -1,17 +1,5 @@ -import { createElement, StyleSheet } from 'react-native-web'; - -function resolve(styleProp, cleanedProps) { - if (styleProp) { - return StyleSheet - ? [styleProp, cleanedProps] - : // Compatibility for arrays of styles in plain react web - styleProp.length - ? Object.assign({}, ...styleProp, cleanedProps) - : Object.assign({}, styleProp, cleanedProps); - } else { - return cleanedProps; - } -} +import { createElement } from 'react-native-web'; +import { resolve } from './lib/resolve'; /** * The `react-native-svg` has some non-standard api's that do not match with the @@ -91,57 +79,22 @@ function prepare(props) { return clean; } -/** - * Return a circle SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Circle SVG. - * @public - */ function Circle(props) { return createElement('circle', prepare(props)); } -/** - * Return a clipPath SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} ClipPath SVG. - * @public - */ function ClipPath(props) { return createElement('clipPath', prepare(props)); } -/** - * Return a defs SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Defs SVG. - * @public - */ function Defs(props) { return createElement('defs', prepare(props)); } -/** - * Return a ellipse SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Ellipse SVG. - * @public - */ function Ellipse(props) { return createElement('ellipse', prepare(props)); } -/** - * Return a g SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} G SVG. - * @public - */ function G(props) { const { x, y, ...rest } = props; @@ -152,216 +105,74 @@ function G(props) { return createElement('g', prepare(rest)); } -/** - * Return a image SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Image SVG. - * @public - */ function Image(props) { return createElement('image', prepare(props)); } -/** - * Return a line SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Line SVG. - * @public - */ function Line(props) { return createElement('line', prepare(props)); } -/** - * Return a linearGradient SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} LinearGradient SVG. - * @public - */ function LinearGradient(props) { return createElement('linearGradient', prepare(props)); } -/** - * Return a path SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Path SVG. - * @public - */ function Path(props) { return createElement('path', prepare(props)); } -/** - * Return a polygon SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Polygon SVG. - * @public - */ function Polygon(props) { return createElement('polygon', prepare(props)); } -/** - * Return a polyline SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Polyline SVG. - * @public - */ function Polyline(props) { return createElement('polyline', prepare(props)); } -/** - * Return a radialGradient SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} RadialGradient SVG. - * @public - */ function RadialGradient(props) { return createElement('radialGradient', prepare(props)); } -/** - * Return a rect SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Rect SVG. - * @public - */ function Rect(props) { return createElement('rect', prepare(props)); } -/** - * Return a stop SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Stop SVG. - * @public - */ function Stop(props) { return createElement('stop', prepare(props)); } -/** - * Return a SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} SVG. - * @public - */ function Svg(props) { - const { title, ...rest } = props; - - if (title) { - return createElement( - 'svg', - { role: 'img', 'aria-label': '[title]', ...prepare(rest) }, - [createElement('title', {}, title), props.children], - ); - } - - return createElement('svg', prepare(rest)); + return createElement('svg', prepare(props)); } -/** - * Return a symbol SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Symbol SVG. - * @public - */ function Symbol(props) { return createElement('symbol', prepare(props)); } -/** - * Return a text SVG element. - * - * @returns {React.Component} Text SVG. - * @public - * @param {Object} props The properties that are spread on the SVG element. - * @param {String} props.x x position - * @param {String} props.y y position - * @param {String} props.dx delta x - * @param {String} props.dy delta y - * @param {String} props.rotate rotation - */ function Text(props) { return createElement('text', prepare(props)); } -/** - * Return a tspan SVG element. - * - * @returns {React.Component} TSpan SVG. - * @public - * @param {Object} props The properties that are spread on the SVG element. - * @param {String} props.x x position - * @param {String} props.y y position - * @param {String} props.dx delta x - * @param {String} props.dy delta y - * @param {String} props.rotate rotation - */ function TSpan(props) { return createElement('tspan', prepare(props)); } -/** - * Return a textpath SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} TextPath SVG. - * @public - */ function TextPath(props) { return createElement('textPath', prepare(props)); } -/** - * Return a use SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Use SVG. - * @public - */ function Use(props) { return createElement('use', prepare(props)); } -/** - * Return a mask SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Use SVG. - * @public - */ function Mask(props) { return createElement('mask', prepare(props)); } -/** - * Return a pattern SVG element. - * - * @param {Object} props The properties that are spread on the SVG element. - * @returns {React.Component} Use SVG. - * @public - */ function Pattern(props) { return createElement('pattern', prepare(props)); } -// -// Expose everything in the same way as `react-native-svg` is doing. -// export { Circle, ClipPath, diff --git a/lib/resolve.js b/lib/resolve.js new file mode 100644 index 00000000..661fea27 --- /dev/null +++ b/lib/resolve.js @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native-web'; + +// Kept in separate file, to avoid name collision with Symbol element +export function resolve(styleProp, cleanedProps) { + if (styleProp) { + return StyleSheet + ? [styleProp, cleanedProps] + : // Compatibility for arrays of styles in plain react web + styleProp[Symbol.iterator] + ? Object.assign({}, ...styleProp, cleanedProps) + : Object.assign({}, styleProp, cleanedProps); + } else { + return cleanedProps; + } +} From bd6ffcafe3621b9f286726f381347f588ab0b017 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Mar 2019 03:39:30 +0200 Subject: [PATCH 37/71] [js] optimize object allocations --- elements/TSpan.js | 30 ++++++++------------ elements/Text.js | 30 ++++++++------------ elements/TextPath.js | 65 +++++++++++++++++++++----------------------- 3 files changed, 55 insertions(+), 70 deletions(-) diff --git a/elements/TSpan.js b/elements/TSpan.js index 18524fde..94872861 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -15,29 +15,23 @@ export default class TSpan extends Shape { props.matrix = matrix; } const prop = propsAndStyles(props); - const text = pickNotNil(extractText(prop, false)); - this.root.setNativeProps({ - ...prop, - ...text, - }); + Object.assign(prop, pickNotNil(extractText(prop, false))); + this.root.setNativeProps(prop); }; render() { const prop = propsAndStyles(this.props); - return ( - + const props = extractProps( + { + ...prop, + x: null, + y: null, + }, + this, ); + Object.assign(props, extractText(prop, false)); + props.ref = this.refMethod; + return ; } } diff --git a/elements/Text.js b/elements/Text.js index 2fcb7801..24a586cd 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -16,29 +16,23 @@ export default class Text extends Shape { props.matrix = matrix; } const prop = propsAndStyles(props); - const text = pickNotNil(extractText(prop, true)); - this.root.setNativeProps({ - ...prop, - ...text, - }); + Object.assign(prop, pickNotNil(extractText(prop, true))); + this.root.setNativeProps(prop); }; render() { const prop = propsAndStyles(this.props); - return ( - + const props = extractProps( + { + ...prop, + x: null, + y: null, + }, + this, ); + Object.assign(props, extractText(prop, true)); + props.ref = this.refMethod; + return ; } } diff --git a/elements/TextPath.js b/elements/TextPath.js index 3ba7a754..7d2ddd2c 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -15,11 +15,8 @@ export default class TextPath extends Shape { if (matrix) { props.matrix = matrix; } - const text = pickNotNil(extractText(props, true)); - this.root.setNativeProps({ - ...props, - ...text, - }); + Object.assign(props, pickNotNil(extractText(props, true))); + this.root.setNativeProps(props); }; render() { @@ -27,45 +24,45 @@ export default class TextPath extends Shape { children, xlinkHref, href = xlinkHref, - startOffset, + startOffset = 0, method, spacing, side, alignmentBaseline, midLine, - ...props + ...prop } = this.props; const matched = href && href.match(idPattern); const match = matched && matched[1]; if (match) { - return ( - + const props = extractProps( + { + ...propsAndStyles(prop), + x: null, + y: null, + }, + this, ); + Object.assign( + props, + extractText( + { + children, + }, + true, + ), + { + href: match, + startOffset, + method, + spacing, + side, + alignmentBaseline, + midLine, + }, + ); + props.ref = this.refMethod; + return ; } console.warn( From 74b0e3d99fdd6b6dd25648f2161dd7c555687318 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 10 Mar 2019 03:51:59 +0200 Subject: [PATCH 38/71] [web] refactor exports and cleanup docs --- index.web.js | 79 +++++++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/index.web.js b/index.web.js index 764c66bf..39ad6a12 100644 --- a/index.web.js +++ b/index.web.js @@ -11,6 +11,7 @@ import { resolve } from './lib/resolve'; * @private */ function prepare(props) { + /* eslint-disable eqeqeq */ const { translate, scale, @@ -27,12 +28,8 @@ function prepare(props) { ...clean } = props; - // Correctly apply the transformation properties. - // To apply originX and originY we need to translate the element on those values and - // translate them back once the element is scaled, rotated and skewed. const transform = []; - /* eslint-disable eqeqeq */ if (originX != null || originY != null) { transform.push(`translate(${originX || 0}, ${originY || 0})`); } @@ -42,7 +39,7 @@ function prepare(props) { if (scale != null) { transform.push(`scale(${scale})`); } - // rotation maps to rotate, not to collide with the text rotate attribute + // rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block) if (rotation != null) { transform.push(`rotate(${rotation})`); } @@ -61,6 +58,7 @@ function prepare(props) { } const styles = {}; + if (fontFamily != null) { styles.fontFamily = fontFamily; } @@ -73,29 +71,29 @@ function prepare(props) { if (fontStyle != null) { styles.fontStyle = fontStyle; } - /* eslint-enable eqeqeq */ + clean.style = resolve(style, styles); return clean; } -function Circle(props) { +export function Circle(props) { return createElement('circle', prepare(props)); } -function ClipPath(props) { +export function ClipPath(props) { return createElement('clipPath', prepare(props)); } -function Defs(props) { +export function Defs(props) { return createElement('defs', prepare(props)); } -function Ellipse(props) { +export function Ellipse(props) { return createElement('ellipse', prepare(props)); } -function G(props) { +export function G(props) { const { x, y, ...rest } = props; if ((x || y) && !rest.translate) { @@ -105,97 +103,72 @@ function G(props) { return createElement('g', prepare(rest)); } -function Image(props) { +export function Image(props) { return createElement('image', prepare(props)); } -function Line(props) { +export function Line(props) { return createElement('line', prepare(props)); } -function LinearGradient(props) { +export function LinearGradient(props) { return createElement('linearGradient', prepare(props)); } -function Path(props) { +export function Path(props) { return createElement('path', prepare(props)); } -function Polygon(props) { +export function Polygon(props) { return createElement('polygon', prepare(props)); } -function Polyline(props) { +export function Polyline(props) { return createElement('polyline', prepare(props)); } -function RadialGradient(props) { +export function RadialGradient(props) { return createElement('radialGradient', prepare(props)); } -function Rect(props) { +export function Rect(props) { return createElement('rect', prepare(props)); } -function Stop(props) { +export function Stop(props) { return createElement('stop', prepare(props)); } -function Svg(props) { +export function Svg(props) { return createElement('svg', prepare(props)); } -function Symbol(props) { +export function Symbol(props) { return createElement('symbol', prepare(props)); } -function Text(props) { +export function Text(props) { return createElement('text', prepare(props)); } -function TSpan(props) { +export function TSpan(props) { return createElement('tspan', prepare(props)); } -function TextPath(props) { +export function TextPath(props) { return createElement('textPath', prepare(props)); } -function Use(props) { +export function Use(props) { return createElement('use', prepare(props)); } -function Mask(props) { +export function Mask(props) { return createElement('mask', prepare(props)); } -function Pattern(props) { +export function Pattern(props) { return createElement('pattern', prepare(props)); } -export { - Circle, - ClipPath, - Defs, - Ellipse, - G, - Image, - Line, - LinearGradient, - Mask, - Path, - Pattern, - Polygon, - Polyline, - RadialGradient, - Rect, - Stop, - Svg, - Symbol, - TSpan, - Text, - TextPath, - Use, -}; - export default Svg; From 6bd27dfeba3422482b43b15bef287b72d1bf7d54 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 02:43:06 +0200 Subject: [PATCH 39/71] [bugfix] Fix calling toDataUrl early and with options Enable calling toDataUrl as soon as you get a ref to the svg root. Fix invalidation and rendering with options for width and height. --- .../main/java/com/horcrux/svg/SvgView.java | 23 ++++++++++++- .../java/com/horcrux/svg/SvgViewManager.java | 10 ++++++ .../java/com/horcrux/svg/SvgViewModule.java | 34 ++++++++++++++++--- ios/Elements/RNSVGSvgView.h | 2 +- ios/Elements/RNSVGSvgView.m | 12 +++++-- ios/RNSVG.xcodeproj/project.pbxproj | 2 ++ ios/ViewManagers/RNSVGSvgViewManager.m | 30 ++++++++++++---- 7 files changed, 98 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 6709ed62..74c97d3c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -101,8 +101,19 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC if (mBitmap == null) { mBitmap = drawOutput(); } - if (mBitmap != null) + if (mBitmap != null) { canvas.drawBitmap(mBitmap, 0, 0, null); + if (toDataUrlTask != null) { + toDataUrlTask.run(); + toDataUrlTask = null; + } + } + } + + private Runnable toDataUrlTask = null; + + void setToDataUrlTask(Runnable task) { + toDataUrlTask = task; } @Override @@ -138,6 +149,10 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC private boolean mRendered = false; int mTintColor = 0; + boolean isRendered() { + return mRendered; + } + private void clearChildCache() { if (!mRendered) { return; @@ -298,7 +313,10 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC getHeight(), Bitmap.Config.ARGB_8888); + clearChildCache(); drawChildren(new Canvas(bitmap)); + clearChildCache(); + this.invalidate(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); bitmap.recycle(); @@ -312,7 +330,10 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC height, Bitmap.Config.ARGB_8888); + clearChildCache(); drawChildren(new Canvas(bitmap)); + clearChildCache(); + this.invalidate(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); bitmap.recycle(); diff --git a/android/src/main/java/com/horcrux/svg/SvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java index 1ef27013..862e22dd 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -28,9 +28,19 @@ class SvgViewManager extends ReactViewManager { private static final String REACT_CLASS = "RNSVGSvgView"; private static final SparseArray mTagToSvgView = new SparseArray<>(); + private static final SparseArray mTagToRunnable = new SparseArray<>(); static void setSvgView(int tag, SvgView svg) { mTagToSvgView.put(tag, svg); + Runnable task = mTagToRunnable.get(tag); + if (task != null) { + task.run(); + mTagToRunnable.delete(tag); + } + } + + static void runWhenViewIsAvailable(int tag, Runnable task) { + mTagToRunnable.put(tag, task); } static @Nullable SvgView getSvgViewByTag(int tag) { diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java index a5cc4927..5790da74 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java @@ -25,12 +25,33 @@ class SvgViewModule extends ReactContextBaseJavaModule { return "RNSVGSvgViewManager"; } - - @ReactMethod - public void toDataURL(int tag, ReadableMap options, Callback successCallback) { + static public void toDataURL(final int tag, final ReadableMap options, final Callback successCallback, final int attempt) { SvgView svg = SvgViewManager.getSvgViewByTag(tag); - if (svg != null) { + if (svg == null) { + SvgViewManager.runWhenViewIsAvailable(tag, new Runnable() { + @Override + public void run() { + SvgView svg = SvgViewManager.getSvgViewByTag(tag); + if (svg == null) { // Should never happen + return; + } + svg.setToDataUrlTask(new Runnable() { + @Override + public void run() { + toDataURL(tag, options, successCallback, attempt + 1); + } + }); + } + }); + } else if (!svg.isRendered()) { + svg.setToDataUrlTask(new Runnable() { + @Override + public void run() { + toDataURL(tag, options, successCallback, attempt + 1); + } + }); + } else { if (options != null) { successCallback.invoke( svg.toDataURL( @@ -43,4 +64,9 @@ class SvgViewModule extends ReactContextBaseJavaModule { } } } + + @ReactMethod + public void toDataURL(int tag, ReadableMap options, Callback successCallback) { + toDataURL(tag, options, successCallback, 0); + } } diff --git a/ios/Elements/RNSVGSvgView.h b/ios/Elements/RNSVGSvgView.h index 9ba992cf..808c41ab 100644 --- a/ios/Elements/RNSVGSvgView.h +++ b/ios/Elements/RNSVGSvgView.h @@ -52,7 +52,7 @@ - (NSString *)getDataURL; -- (NSString *)getDataURLwithBounds:(CGSize)bounds; +- (NSString *)getDataURLwithBounds:(CGRect)bounds; - (CGRect)getContextBounds; diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index f7c10826..856a83d4 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -250,17 +250,23 @@ - (NSString *)getDataURL { UIGraphicsBeginImageContextWithOptions(_boundingBox.size, NO, 0); + [self clearChildCache]; [self drawRect:_boundingBox]; + [self clearChildCache]; + [self invalidate]; NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; UIGraphicsEndImageContext(); return base64; } -- (NSString *)getDataURLwithBounds:(CGSize)bounds +- (NSString *)getDataURLwithBounds:(CGRect)bounds { - UIGraphicsBeginImageContextWithOptions(bounds, NO, 0); - [self drawRect:_boundingBox]; + UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0); + [self clearChildCache]; + [self drawRect:bounds]; + [self clearChildCache]; + [self invalidate]; NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; UIGraphicsEndImageContext(); diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 8e861247..819a6631 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -251,6 +251,7 @@ 94241669213B0DB800088E93 /* RNSVGPattern.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSVGPattern.h; sourceTree = ""; }; 9424166A213B2FF100088E93 /* RNSVGPatternManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSVGPatternManager.h; sourceTree = ""; }; 9424166C213B302600088E93 /* RNSVGPatternManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSVGPatternManager.m; sourceTree = ""; }; + 94696EE92235A7F200C1D558 /* RNSVGVectorEffect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGVectorEffect.h; path = Utils/RNSVGVectorEffect.h; sourceTree = ""; }; 947F3809214810B800677F2A /* RNSVGMask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGMask.h; path = Elements/RNSVGMask.h; sourceTree = ""; }; 947F380A214810DC00677F2A /* RNSVGMask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGMask.m; path = Elements/RNSVGMask.m; sourceTree = ""; }; 947F380D2148118300677F2A /* RNSVGMaskManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGMaskManager.h; sourceTree = ""; }; @@ -463,6 +464,7 @@ 1039D29A1CE7212C001E90A8 /* Utils */ = { isa = PBXGroup; children = ( + 94696EE92235A7F200C1D558 /* RNSVGVectorEffect.h */, B56895A920352B36004DBF1E /* RNSVGBezierElement.h */, B56895A820352B35004DBF1E /* RNSVGBezierElement.m */, 7F69160D1E3703D800DA6EDC /* RNSVGUnits.h */, diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m index bbd015be..c07a9f08 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.m +++ b/ios/ViewManagers/RNSVGSvgViewManager.m @@ -8,6 +8,7 @@ #import #import +#import #import "RNSVGSvgViewManager.h" #import "RNSVGSvgView.h" @@ -30,15 +31,15 @@ RCT_EXPORT_VIEW_PROPERTY(align, NSString) RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS) RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) -RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback) -{ + +- (void)toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback attempt:(int)attempt { [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { __kindof UIView *view = viewRegistry[reactTag]; + NSString * b64; if ([view isKindOfClass:[RNSVGSvgView class]]) { RNSVGSvgView *svg = view; if (options == nil) { - RNSVGSvgView *svg = view; - callback(@[[svg getDataURL]]); + b64 = [svg getDataURL]; } else { id width = [options objectForKey:@"width"]; id height = [options objectForKey:@"height"]; @@ -52,13 +53,30 @@ RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary * NSNumber* h = height; NSInteger hi = (NSInteger)[h intValue]; - CGSize bounds = CGSizeMake(wi, hi); - callback(@[[svg getDataURLwithBounds:bounds]]); + CGRect bounds = CGRectMake(0, 0, wi, hi); + b64 = [svg getDataURLwithBounds:bounds]; } } else { RCTLogError(@"Invalid svg returned frin registry, expecting RNSVGSvgView, got: %@", view); + return; + } + if (b64) { + callback(@[b64]); + } else if (attempt < 1) { + void (^retryBlock)(void) = ^{ + [self toDataURL:reactTag options:options callback:callback attempt:(attempt + 1)]; + }; + + RCTExecuteOnUIManagerQueue(retryBlock); + } else { + callback(@[]); } }]; } +RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback) +{ + [self toDataURL:reactTag options:options callback:callback attempt:0]; +} + @end From a6bd2d07c043416d336ae64e837c8e763e15ae7d Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 03:13:07 +0200 Subject: [PATCH 40/71] 9.3.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5edf7825..02fe38fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.2.4", + "version": "9.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4fce0bf0..6d842baa 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.2.4", + "version": "9.3.0", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 221e23a737ec69f0f2b35f341506e44b62610021 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 03:36:03 +0200 Subject: [PATCH 41/71] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 431df994..f0cc8c1b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Version](https://img.shields.io/npm/v/react-native-svg.svg)](https://www.npmjs.com/package/react-native-svg) [![NPM](https://img.shields.io/npm/dm/react-native-svg.svg)](https://www.npmjs.com/package/react-native-svg) -`react-native-svg` provides SVG support to React Native on iOS and Android. +`react-native-svg` provides SVG support to React Native on iOS and Android, and a compatibility layer for the web. [Check out the demo](https://snack.expo.io/@msand/react-native-svg-example) From 9a30e47ab94b2b952c7d018145debed9772a3790 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 04:36:58 +0200 Subject: [PATCH 42/71] Fix #961 ### java compiler error error: package com.facebook.infer.annotation does not exist --- .../src/main/java/com/horcrux/svg/RenderableViewManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index f6bf0e36..bd714309 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -13,7 +13,6 @@ import android.graphics.Matrix; import android.view.View; import android.view.ViewGroup; -import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableArray; @@ -179,7 +178,9 @@ class RenderableViewManager extends ViewGroupManager { } private static void decomposeMatrix() { - Assertions.assertCondition(sTransformDecompositionArray.length == 16); + if (sTransformDecompositionArray.length != 16) { + throw new AssertionError(); + } // output values final double[] perspective = sMatrixDecompositionContext.perspective; From 84e6f2402c2e9843025c44fdabd057282e4720f9 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 04:37:10 +0200 Subject: [PATCH 43/71] 9.3.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02fe38fc..10feb327 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.0", + "version": "9.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6d842baa..9bd86d53 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.0", + "version": "9.3.1", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 5505d3f11f1d64d60074111c309401f7dd03850f Mon Sep 17 00:00:00 2001 From: Gertjan Reynaert Date: Mon, 11 Mar 2019 09:25:59 +0100 Subject: [PATCH 44/71] Fix typescript error in index.d.ts When upgrading to react-native-svg 9.3.1 I got these error messages in Typescript ``` node_modules/react-native-svg/index.d.ts:105:19 - error TS1005: ';' expected. 105 VectorEffectProps { ~ node_modules/react-native-svg/index.d.ts:106:16 - error TS1109: Expression expected. 106 vectorEffect?: "none" | "non-scaling-stroke" | "nonScalingStroke" | "default" | "inherit" | "uri"; ~ Found 2 errors. ``` It seems to me that this was caused by forgetting to add the `interface` keyword before `VectorEffectProps`. This pr fixes that. --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 889d1095..5f1166e8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -102,7 +102,7 @@ export interface ClipProps { clipPath?: string } -VectorEffectProps { +interface VectorEffectProps { vectorEffect?: "none" | "non-scaling-stroke" | "nonScalingStroke" | "default" | "inherit" | "uri"; } From 3321ba19741b48e740a8ddf1faa96117c1d60e72 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 16:56:55 +0200 Subject: [PATCH 45/71] 9.3.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10feb327..eb2d4ef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.1", + "version": "9.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9bd86d53..23e61cb8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.1", + "version": "9.3.2", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From cdf862ba73b0e17e574bfc25970f6363915128d2 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 11 Mar 2019 16:59:08 +0200 Subject: [PATCH 46/71] 9.3.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb2d4ef2..f769cc81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.2", + "version": "9.3.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 23e61cb8..dfd5e0ce 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.2", + "version": "9.3.3", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From ec9637bcc5b43633eaa3413c99001ac4da946104 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 16 Mar 2019 03:07:26 +0200 Subject: [PATCH 47/71] Fix uncaught exception 'NSUnknownKeyException' #959 --- ios/Elements/RNSVGGroup.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index fc75689a..7a26cd67 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -174,7 +174,7 @@ } if (!event) { - NSPredicate *const anyActive = [NSPredicate predicateWithFormat:@"active == TRUE"]; + NSPredicate *const anyActive = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@ AND active == TRUE", [RNSVGNode class]]; NSArray *const filtered = [self.subviews filteredArrayUsingPredicate:anyActive]; if ([filtered count] != 0) { return [filtered.lastObject hitTest:transformed withEvent:event]; From 3cff87fd4f357ca33a9671ce86af679739ee16fc Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 17 Mar 2019 03:27:53 +0200 Subject: [PATCH 48/71] Fix compatibility with svgr and images #971 --- elements/Image.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/elements/Image.js b/elements/Image.js index 7b6e13b5..f4c36b98 100644 --- a/elements/Image.js +++ b/elements/Image.js @@ -39,7 +39,9 @@ export default class SvgImage extends Shape { height={height} meetOrSlice={meetOrSliceTypes[modes[1]] || 0} align={alignEnum[modes[0]] || 'xMidYMid'} - src={Image.resolveAssetSource(href)} + src={Image.resolveAssetSource( + typeof href === 'string' ? { uri: href } : href, + )} /> ); } From ccb06e892901bfeb46a2a7917f4274aa4b0c7311 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 18 Mar 2019 20:09:58 +0200 Subject: [PATCH 49/71] Fix Dynamic paths within don't update #973 --- ios/RNSVGRenderable.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index d9a898b2..e19db35a 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -508,12 +508,12 @@ UInt32 saturate(CGFloat value) { - (void)mergeProperties:(__kindof RNSVGRenderable *)target { - self.merging = true; NSArray *targetAttributeList = [target getAttributeList]; if (targetAttributeList.count == 0) { return; } + self.merging = true; NSMutableArray* attributeList = [self.propList mutableCopy]; _originProperties = [[NSMutableDictionary alloc] init]; From 0bea45b5e713795393b31957bc2a85681315cf42 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 18 Mar 2019 20:10:58 +0200 Subject: [PATCH 50/71] 9.3.4 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f769cc81..cabf3473 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.3", + "version": "9.3.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dfd5e0ce..bb0439a4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.3", + "version": "9.3.4", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 0e48d439f1f2020ab1ed48fc6ae121b9d30e9e32 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 18 Mar 2019 20:45:26 +0200 Subject: [PATCH 51/71] [android] Fix SVG.toDataURL exception #948 --- .../java/com/horcrux/svg/SvgViewModule.java | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java index 5790da74..42e749e7 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java @@ -14,6 +14,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UiThreadUtil; class SvgViewModule extends ReactContextBaseJavaModule { SvgViewModule(ReactApplicationContext reactContext) { @@ -26,43 +27,50 @@ class SvgViewModule extends ReactContextBaseJavaModule { } static public void toDataURL(final int tag, final ReadableMap options, final Callback successCallback, final int attempt) { - SvgView svg = SvgViewManager.getSvgViewByTag(tag); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + SvgView svg = SvgViewManager.getSvgViewByTag(tag); - if (svg == null) { - SvgViewManager.runWhenViewIsAvailable(tag, new Runnable() { - @Override - public void run() { - SvgView svg = SvgViewManager.getSvgViewByTag(tag); - if (svg == null) { // Should never happen - return; - } - svg.setToDataUrlTask(new Runnable() { - @Override - public void run() { - toDataURL(tag, options, successCallback, attempt + 1); + if (svg == null) { + SvgViewManager.runWhenViewIsAvailable(tag, new Runnable() { + @Override + public void run() { + SvgView svg = SvgViewManager.getSvgViewByTag(tag); + if (svg == null) { // Should never happen + return; + } + svg.setToDataUrlTask(new Runnable() { + @Override + public void run() { + toDataURL(tag, options, successCallback, attempt + 1); + } + }); + } + }); + } else if (!svg.isRendered()) { + svg.setToDataUrlTask(new Runnable() { + @Override + public void run() { + toDataURL(tag, options, successCallback, attempt + 1); + } + }); + } else { + if (options != null) { + successCallback.invoke( + svg.toDataURL( + options.getInt("width"), + options.getInt("height") + ) + ); + } else { + successCallback.invoke(svg.toDataURL()); + } } - }); + } } - }); - } else if (!svg.isRendered()) { - svg.setToDataUrlTask(new Runnable() { - @Override - public void run() { - toDataURL(tag, options, successCallback, attempt + 1); - } - }); - } else { - if (options != null) { - successCallback.invoke( - svg.toDataURL( - options.getInt("width"), - options.getInt("height") - ) - ); - } else { - successCallback.invoke(svg.toDataURL()); - } - } + ); } @ReactMethod From 1106dbae2e303e945c524e53c8dede7fcca09b84 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 18 Mar 2019 20:45:42 +0200 Subject: [PATCH 52/71] 9.3.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cabf3473..bf01150b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.4", + "version": "9.3.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index bb0439a4..63f8b516 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.4", + "version": "9.3.5", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From eaec9b9988a007b21dfbf4e71ecff237c9097933 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 21 Mar 2019 02:49:36 +0200 Subject: [PATCH 53/71] [android] Fix calculation / clearing of cached glyph advance #977 --- android/src/main/java/com/horcrux/svg/TSpanView.java | 5 +++++ android/src/main/java/com/horcrux/svg/TextView.java | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java index 2663b153..6aff115f 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanView.java +++ b/android/src/main/java/com/horcrux/svg/TSpanView.java @@ -70,6 +70,11 @@ class TSpanView extends TextView { super.invalidate(); } + void clearCache() { + mCachedPath = null; + super.clearCache(); + } + @Override void draw(Canvas canvas, Paint paint, float opacity) { if (mContent != null) { diff --git a/android/src/main/java/com/horcrux/svg/TextView.java b/android/src/main/java/com/horcrux/svg/TextView.java index 5aa98ec9..f4485d97 100644 --- a/android/src/main/java/com/horcrux/svg/TextView.java +++ b/android/src/main/java/com/horcrux/svg/TextView.java @@ -51,7 +51,7 @@ class TextView extends GroupView { return; } super.invalidate(); - clearChildCache(); + getTextContainer().clearChildCache(); } void clearCache() { @@ -246,4 +246,13 @@ class TextView extends GroupView { return advance; } + TextView getTextContainer() { + TextView node = this; + ViewParent parent = this.getParent(); + while (parent instanceof TextView) { + node = (TextView) parent; + parent = node.getParent(); + } + return node; + } } From 70ac80b297b0d39cad1e2f78f07429fbdc3258e8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 21 Mar 2019 18:21:40 +0200 Subject: [PATCH 54/71] Improved anchored text chunk logic #570 --- android/src/main/java/com/horcrux/svg/TextView.java | 2 +- ios/Text/RNSVGText.m | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TextView.java b/android/src/main/java/com/horcrux/svg/TextView.java index f4485d97..0fa37376 100644 --- a/android/src/main/java/com/horcrux/svg/TextView.java +++ b/android/src/main/java/com/horcrux/svg/TextView.java @@ -221,7 +221,7 @@ class TextView extends GroupView { TextView node = this; ViewParent parent = this.getParent(); for (int i = font.size() - 1; i >= 0; i--) { - if (!(parent instanceof TextView) || font.get(i).textAnchor == TextProperties.TextAnchor.start) { + if (!(parent instanceof TextView) || font.get(i).textAnchor == TextProperties.TextAnchor.start || node.mPositionX != null) { return node; } node = (TextView) parent; diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index b9e69996..ee94c96a 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -264,7 +264,8 @@ for (NSInteger i = [font count] - 1; i >= 0; i--) { RNSVGFontData* fontData = [font objectAtIndex:i]; if (![parent isKindOfClass:[RNSVGText class]] || - fontData->textAnchor == RNSVGTextAnchorStart) { + fontData->textAnchor == RNSVGTextAnchorStart || + node.positionX != nil) { return node; } node = (RNSVGText*) parent; From 27b49b2e4dd8ef0b6e5235b127b4aec9e84ac514 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 21 Mar 2019 19:06:39 +0200 Subject: [PATCH 55/71] 9.3.6 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf01150b..285982ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.5", + "version": "9.3.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 63f8b516..357e3f5b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.5", + "version": "9.3.6", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From ee6139cb03e8cfbbee81945d5f9e5ab7b5ffdbfa Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 21 Mar 2019 19:41:36 +0200 Subject: [PATCH 56/71] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f0cc8c1b..04a6abae 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,8 @@ Verify that it is still an issue with the latest version. If so, open a new issu react-native info ``` +If you suspect that you've found a spec conformance bug, then you can test using your component in a react-native-web project by forking this codesandbox, to see how different browsers render the same content: https://codesandbox.io/s/pypn6mn3y7 + ### Usage Here's a simple example. To render output like this: From 9321aa504a88429781fa95942588477e261738c2 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 22 Mar 2019 21:38:30 +0200 Subject: [PATCH 57/71] [android] Fix clearing of cached glyph advance on add/remove children #977 --- .../src/main/java/com/horcrux/svg/RenderableViewManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index bd714309..c69810ac 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -1061,6 +1061,9 @@ class RenderableViewManager extends ViewGroupManager { if (view!= null) { view.invalidate(); } + if (node instanceof TextView) { + ((TextView)node).getTextContainer().clearChildCache(); + } } @Override From 3e4505caa484188eb4dcc94e57011be9bdd0ce94 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 31 Mar 2019 18:29:30 +0300 Subject: [PATCH 58/71] 9.3.7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 285982ad..e6fe4f42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.6", + "version": "9.3.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 357e3f5b..ca783564 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.6", + "version": "9.3.7", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 5389209a64debc4b9242232b2fa51403e33aea35 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 31 Mar 2019 18:48:57 +0300 Subject: [PATCH 59/71] [iOS] Fix clipRule="evenodd" https://github.com/react-native-community/react-native-svg/issues/983 --- ios/RNSVGNode.m | 38 ++++---------------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 7d491e21..66d2507a 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -23,7 +23,6 @@ BOOL _transparent; RNSVGClipPath *_clipNode; CGPathRef _cachedClipPath; - CGImageRef _clipMask; CGFloat canvasWidth; CGFloat canvasHeight; CGFloat canvasDiagonal; @@ -225,10 +224,8 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return; } CGPathRelease(_cachedClipPath); - CGImageRelease(_clipMask); _cachedClipPath = nil; _clipPath = clipPath; - _clipMask = nil; [self invalidate]; } @@ -238,10 +235,8 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return; } CGPathRelease(_cachedClipPath); - CGImageRelease(_clipMask); _cachedClipPath = nil; _clipRule = clipRule; - _clipMask = nil; [self invalidate]; } @@ -287,24 +282,6 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; CGPathRelease(_cachedClipPath); } _cachedClipPath = CGPathRetain([_clipNode getPath:context]); - if (_clipMask) { - CGImageRelease(_clipMask); - } - if ([_clipNode isSimpleClipPath]) { - _clipMask = nil; - } else { - CGRect bounds = CGContextGetClipBoundingBox(context); - CGSize size = bounds.size; - - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - CGContextRef newContext = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(newContext, 0.0, size.height); - CGContextScaleCTM(newContext, 1.0, -1.0); - - [_clipNode renderLayerTo:newContext rect:bounds]; - _clipMask = CGBitmapContextCreateImage(newContext); - UIGraphicsEndImageContext(); - } } return _cachedClipPath; @@ -315,16 +292,11 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; CGPathRef clipPath = [self getClipPath:context]; if (clipPath) { - if (!_clipMask) { - CGContextAddPath(context, clipPath); - if (_clipNode.clipRule == kRNSVGCGFCRuleEvenodd) { - CGContextEOClip(context); - } else { - CGContextClip(context); - } + CGContextAddPath(context, clipPath); + if (_clipNode.clipRule == kRNSVGCGFCRuleEvenodd) { + CGContextEOClip(context); } else { - CGRect bounds = CGContextGetClipBoundingBox(context); - CGContextClipToMask(context, bounds, _clipMask); + CGContextClip(context); } } } @@ -551,9 +523,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; { CGPathRelease(_cachedClipPath); CGPathRelease(_strokePath); - CGImageRelease(_clipMask); CGPathRelease(_path); - _clipMask = nil; } @end From f63141e7798def20f4af5b3943d8cdd819e96c91 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 1 Apr 2019 04:35:30 +0300 Subject: [PATCH 60/71] Revert "[iOS] Fix clipRule="evenodd"" This reverts commit 5389209 --- ios/RNSVGNode.m | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 66d2507a..7d491e21 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -23,6 +23,7 @@ BOOL _transparent; RNSVGClipPath *_clipNode; CGPathRef _cachedClipPath; + CGImageRef _clipMask; CGFloat canvasWidth; CGFloat canvasHeight; CGFloat canvasDiagonal; @@ -224,8 +225,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return; } CGPathRelease(_cachedClipPath); + CGImageRelease(_clipMask); _cachedClipPath = nil; _clipPath = clipPath; + _clipMask = nil; [self invalidate]; } @@ -235,8 +238,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return; } CGPathRelease(_cachedClipPath); + CGImageRelease(_clipMask); _cachedClipPath = nil; _clipRule = clipRule; + _clipMask = nil; [self invalidate]; } @@ -282,6 +287,24 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; CGPathRelease(_cachedClipPath); } _cachedClipPath = CGPathRetain([_clipNode getPath:context]); + if (_clipMask) { + CGImageRelease(_clipMask); + } + if ([_clipNode isSimpleClipPath]) { + _clipMask = nil; + } else { + CGRect bounds = CGContextGetClipBoundingBox(context); + CGSize size = bounds.size; + + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + CGContextRef newContext = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(newContext, 0.0, size.height); + CGContextScaleCTM(newContext, 1.0, -1.0); + + [_clipNode renderLayerTo:newContext rect:bounds]; + _clipMask = CGBitmapContextCreateImage(newContext); + UIGraphicsEndImageContext(); + } } return _cachedClipPath; @@ -292,11 +315,16 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; CGPathRef clipPath = [self getClipPath:context]; if (clipPath) { - CGContextAddPath(context, clipPath); - if (_clipNode.clipRule == kRNSVGCGFCRuleEvenodd) { - CGContextEOClip(context); + if (!_clipMask) { + CGContextAddPath(context, clipPath); + if (_clipNode.clipRule == kRNSVGCGFCRuleEvenodd) { + CGContextEOClip(context); + } else { + CGContextClip(context); + } } else { - CGContextClip(context); + CGRect bounds = CGContextGetClipBoundingBox(context); + CGContextClipToMask(context, bounds, _clipMask); } } } @@ -523,7 +551,9 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; { CGPathRelease(_cachedClipPath); CGPathRelease(_strokePath); + CGImageRelease(_clipMask); CGPathRelease(_path); + _clipMask = nil; } @end From 134cbad37ebe432f20118f1be33725f6bc31af2b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 1 Apr 2019 04:32:21 +0300 Subject: [PATCH 61/71] [iOS] Fix clipRule="evenodd" https://github.com/react-native-community/react-native-svg/issues/983 --- ios/RNSVGNode.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 7d491e21..0c622d21 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -290,7 +290,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; if (_clipMask) { CGImageRelease(_clipMask); } - if ([_clipNode isSimpleClipPath]) { + if ([_clipNode isSimpleClipPath] || _clipNode.clipRule == kRNSVGCGFCRuleEvenodd) { _clipMask = nil; } else { CGRect bounds = CGContextGetClipBoundingBox(context); From 76b8b824e27d0b0833f74345d34b39f8d1de82be Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 1 Apr 2019 04:34:16 +0300 Subject: [PATCH 62/71] Implement support for Use element inside ClipPath ```jsx import React from 'react'; import { View } from 'react-native'; import Svg, { Defs, ClipPath, Path, Rect, G, Text, Polygon, Use, } from 'react-native-svg'; const SvgComponent = props => ( {'non-zero clip-rule'} {'non-zero fill-rule'} {'even-odd clip-rule'} {'even-odd fill-rule'} ); const SvgComponent2 = props => ( ); const SvgComponent3 = ({ clipRule = 'nonzero' }) => ( Hello world ); export default class App extends React.Component { render() { return ( ); } } ``` --- .../main/java/com/horcrux/svg/UseView.java | 70 ++++++++++++------- ios/Elements/RNSVGUse.m | 14 ++++ 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/UseView.java b/android/src/main/java/com/horcrux/svg/UseView.java index c5b14d5f..b4f6445e 100644 --- a/android/src/main/java/com/horcrux/svg/UseView.java +++ b/android/src/main/java/com/horcrux/svg/UseView.java @@ -11,6 +11,7 @@ package com.horcrux.svg; import android.annotation.SuppressLint; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; @@ -66,31 +67,32 @@ class UseView extends RenderableView { void draw(Canvas canvas, Paint paint, float opacity) { VirtualView template = getSvgView().getDefinedTemplate(mHref); - if (template != null) { - canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY)); - if (template instanceof RenderableView) { - ((RenderableView)template).mergeProperties(this); - } - - int count = template.saveAndSetupCanvas(canvas); - clip(canvas, paint); - - if (template instanceof SymbolView) { - SymbolView symbol = (SymbolView)template; - symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mW), (float) relativeOnHeight(mH)); - } else { - template.draw(canvas, paint, opacity * mOpacity); - } - - this.setClientRect(template.getClientRect()); - - template.restoreCanvas(canvas, count); - if (template instanceof RenderableView) { - ((RenderableView)template).resetProperties(); - } - } else { + if (template == null) { FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " + - "template named: " + mHref + " is not defined."); + "template named: " + mHref + " is not defined."); + return; + } + + canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY)); + if (template instanceof RenderableView) { + ((RenderableView)template).mergeProperties(this); + } + + int count = template.saveAndSetupCanvas(canvas); + clip(canvas, paint); + + if (template instanceof SymbolView) { + SymbolView symbol = (SymbolView)template; + symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mW), (float) relativeOnHeight(mH)); + } else { + template.draw(canvas, paint, opacity * mOpacity); + } + + this.setClientRect(template.getClientRect()); + + template.restoreCanvas(canvas, count); + if (template instanceof RenderableView) { + ((RenderableView)template).resetProperties(); } } @@ -105,6 +107,12 @@ class UseView extends RenderableView { mInvTransform.mapPoints(dst); VirtualView template = getSvgView().getDefinedTemplate(mHref); + if (template == null) { + FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " + + "template named: " + mHref + " is not defined."); + return -1; + } + int hitChild = template.hitTest(dst); if (hitChild != -1) { return (template.isResponsible() || hitChild != template.getId()) ? hitChild : getId(); @@ -115,7 +123,17 @@ class UseView extends RenderableView { @Override Path getPath(Canvas canvas, Paint paint) { - // todo: - return new Path(); + VirtualView template = getSvgView().getDefinedTemplate(mHref); + if (template == null) { + FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " + + "template named: " + mHref + " is not defined."); + return null; + } + Path path = template.getPath(canvas, paint); + Path use = new Path(); + Matrix m = new Matrix(); + m.setTranslate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY)); + path.transform(m, use); + return use; } } diff --git a/ios/Elements/RNSVGUse.m b/ios/Elements/RNSVGUse.m index 5a42b568..14047b06 100644 --- a/ios/Elements/RNSVGUse.m +++ b/ios/Elements/RNSVGUse.m @@ -89,6 +89,9 @@ } else if (self.href) { // TODO: calling yellow box here RCTLogWarn(@"`Use` element expected a pre-defined svg template as `href` prop, template named: %@ is not defined.", self.href); + return; + } else { + return; } CGRect bounds = template.clientRect; self.clientRect = bounds; @@ -120,5 +123,16 @@ return nil; } +- (CGPathRef)getPath: (CGContextRef)context +{ + CGAffineTransform transform = CGAffineTransformMakeTranslation([self relativeOnWidth:self.x], [self relativeOnHeight:self.y]); + RNSVGNode const* template = [self.svgView getDefinedTemplate:self.href]; + if (!template) { + return nil; + } + CGPathRef path = [template getPath:context]; + return CGPathCreateCopyByTransformingPath(path, &transform); +} + @end From fde018b8aa5154dc1daad2a964abdba0fe58dc51 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 1 Apr 2019 20:02:48 +0300 Subject: [PATCH 63/71] [android] Clear path cache to fix fill rule when > 1 Use with same href --- android/src/main/java/com/horcrux/svg/UseView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/horcrux/svg/UseView.java b/android/src/main/java/com/horcrux/svg/UseView.java index b4f6445e..487de47f 100644 --- a/android/src/main/java/com/horcrux/svg/UseView.java +++ b/android/src/main/java/com/horcrux/svg/UseView.java @@ -73,6 +73,7 @@ class UseView extends RenderableView { return; } + template.clearCache(); canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY)); if (template instanceof RenderableView) { ((RenderableView)template).mergeProperties(this); From 36674ee433e83e1f489128b0ce1a30ecf6dd885a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 13 Apr 2019 22:09:30 +0300 Subject: [PATCH 64/71] [web] Support Animated --- index.web.js | 165 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 61 deletions(-) diff --git a/index.web.js b/index.web.js index 39ad6a12..2d830be6 100644 --- a/index.web.js +++ b/index.web.js @@ -1,17 +1,16 @@ import { createElement } from 'react-native-web'; import { resolve } from './lib/resolve'; +import { Component } from 'react'; /** - * The `react-native-svg` has some non-standard api's that do not match with the - * properties that can be applied to web SVG elements. This prepare function removes - * those properties and adds the properties back in their correct location. + * `react-native-svg` supports additional props that aren't defined in the spec. + * This function replaces them in a spec conforming manner. * * @param {Object} props Properties given to us. * @returns {Object} Cleaned object. * @private */ function prepare(props) { - /* eslint-disable eqeqeq */ const { translate, scale, @@ -77,98 +76,142 @@ function prepare(props) { return clean; } -export function Circle(props) { - return createElement('circle', prepare(props)); -} - -export function ClipPath(props) { - return createElement('clipPath', prepare(props)); -} - -export function Defs(props) { - return createElement('defs', prepare(props)); -} - -export function Ellipse(props) { - return createElement('ellipse', prepare(props)); -} - -export function G(props) { - const { x, y, ...rest } = props; - - if ((x || y) && !rest.translate) { - rest.translate = `${x || 0}, ${y || 0}`; +export class Circle extends Component { + render() { + return createElement('circle', prepare(this.props)); } - - return createElement('g', prepare(rest)); } -export function Image(props) { - return createElement('image', prepare(props)); +export class ClipPath extends Component { + render() { + return createElement('clipPath', prepare(this.props)); + } } -export function Line(props) { - return createElement('line', prepare(props)); +export class Defs extends Component { + render() { + return createElement('defs', prepare(this.props)); + } } -export function LinearGradient(props) { - return createElement('linearGradient', prepare(props)); +export class Ellipse extends Component { + render() { + return createElement('ellipse', prepare(this.props)); + } } -export function Path(props) { - return createElement('path', prepare(props)); +export class G extends Component { + render() { + const { x, y, ...rest } = this.props; + + if ((x || y) && !rest.translate) { + rest.translate = `${x || 0}, ${y || 0}`; + } + + return createElement('g', prepare(rest)); + } } -export function Polygon(props) { - return createElement('polygon', prepare(props)); +export class Image extends Component { + render() { + return createElement('image', prepare(this.props)); + } } -export function Polyline(props) { - return createElement('polyline', prepare(props)); +export class Line extends Component { + render() { + return createElement('line', prepare(this.props)); + } } -export function RadialGradient(props) { - return createElement('radialGradient', prepare(props)); +export class LinearGradient extends Component { + render() { + return createElement('linearGradient', prepare(this.props)); + } } -export function Rect(props) { - return createElement('rect', prepare(props)); +export class Path extends Component { + render() { + return createElement('path', prepare(this.props)); + } } -export function Stop(props) { - return createElement('stop', prepare(props)); +export class Polygon extends Component { + render() { + return createElement('polygon', prepare(this.props)); + } } -export function Svg(props) { - return createElement('svg', prepare(props)); +export class Polyline extends Component { + render() { + return createElement('polyline', prepare(this.props)); + } } -export function Symbol(props) { - return createElement('symbol', prepare(props)); +export class RadialGradient extends Component { + render() { + return createElement('radialGradient', prepare(this.props)); + } } -export function Text(props) { - return createElement('text', prepare(props)); +export class Rect extends Component { + render() { + return createElement('rect', prepare(this.props)); + } } -export function TSpan(props) { - return createElement('tspan', prepare(props)); +export class Stop extends Component { + render() { + return createElement('stop', prepare(this.props)); + } } -export function TextPath(props) { - return createElement('textPath', prepare(props)); +export class Svg extends Component { + render() { + return createElement('svg', prepare(this.props)); + } } -export function Use(props) { - return createElement('use', prepare(props)); +export class Symbol extends Component { + render() { + return createElement('symbol', prepare(this.props)); + } } -export function Mask(props) { - return createElement('mask', prepare(props)); +export class Text extends Component { + render() { + return createElement('text', prepare(this.props)); + } } -export function Pattern(props) { - return createElement('pattern', prepare(props)); +export class TSpan extends Component { + render() { + return createElement('tspan', prepare(this.props)); + } +} + +export class TextPath extends Component { + render() { + return createElement('textPath', prepare(this.props)); + } +} + +export class Use extends Component { + render() { + return createElement('use', prepare(this.props)); + } +} + +export class Mask extends Component { + render() { + return createElement('mask', prepare(this.props)); + } +} + +export class Pattern extends Component { + render() { + return createElement('pattern', prepare(this.props)); + } } export default Svg; From 2adf32645a11a633748ff57ed33a1f7a9b2c07b3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 13 Apr 2019 22:09:57 +0300 Subject: [PATCH 65/71] 9.4.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6fe4f42..ba3024c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-svg", - "version": "9.3.7", + "version": "9.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ca783564..3503a11b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "9.3.7", + "version": "9.4.0", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 8f6fbcb74ba6aa24d4e925dc439c87a94313168b Mon Sep 17 00:00:00 2001 From: Asuki Kono Date: Mon, 15 Apr 2019 11:27:55 +0900 Subject: [PATCH 66/71] Add link of w3.org to reference params for path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04a6abae..b005ae87 100644 --- a/README.md +++ b/README.md @@ -605,7 +605,7 @@ The following commands are available for path data: * A = elliptical Arc * Z = closepath -`Note:` All of the commands above can also be expressed with lower letters. Capital letters means absolutely positioned, lower cases means relatively positioned. +`Note:` All of the commands above can also be expressed with lower letters. Capital letters means absolutely positioned, lower cases means relatively positioned. See [Path document of SVG](https://www.w3.org/TR/SVG/paths.html) to know parameters for each command. ```html Date: Thu, 18 Apr 2019 13:05:51 +0300 Subject: [PATCH 67/71] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b005ae87..711d5e3c 100644 --- a/README.md +++ b/README.md @@ -91,9 +91,10 @@ The latest version of react-native-svg should always work in a clean react-nativ | 5.3.0 | 0.46 | | 5.4.1 | 0.47 | | 5.5.1 | >=0.50 | -| 6.0.0 | >=0.50 | -| 7.0.0 | >=0.57.4 | -| 8.0.0 | >=0.57.4 | +| >=6 | >=0.50 | +| >=7 | >=0.57.4 | +| >=8 | >=0.57.4 | +| >=9 | >=0.57.4 | Or, include [this PR](https://github.com/facebook/react-native/pull/17842) manually for v7+ stability on android for older RN ( [included in 0.57-stable](https://github.com/facebook/react-native/commit/d9f5319cf0d9828b29d0e350284b22ce29985042) and newer) From 276a6e93afa9e4bda570e8fd8b0326c5fa0b74a8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 18 Apr 2019 13:11:33 +0300 Subject: [PATCH 68/71] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 711d5e3c..df10776c 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,11 @@ The latest version of react-native-svg should always work in a clean react-nativ | >=8 | >=0.57.4 | | >=9 | >=0.57.4 | -Or, include [this PR](https://github.com/facebook/react-native/pull/17842) manually for v7+ stability on android for older RN ( [included in 0.57-stable](https://github.com/facebook/react-native/commit/d9f5319cf0d9828b29d0e350284b22ce29985042) and newer) +Or, include [this PR](https://github.com/facebook/react-native/pull/17842) manually for v7+ stability on android for older RN ( [included in 0.57-stable](https://github.com/facebook/react-native/commit/d9f5319cf0d9828b29d0e350284b22ce29985042) and newer). +The latest version of v6, v7, v8 and v9 should all work in the latest react-native version. + +v7 and newer requires the patch for making android thread safe, to get native animation support. #### Manually From 0835b500568c542dc815066c0b012be00ddb9bfd Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 19 Apr 2019 04:48:45 +0300 Subject: [PATCH 69/71] Fix translate shorthand and add array support https://github.com/react-native-community/react-native-svg/issues/998 --- lib/extract/extractTransform.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index e1fc9dc6..3bdbc5cb 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -30,6 +30,13 @@ function universal2axis(universal, axisX, axisY, defaultValue) { } else if (coords.length === 1) { x = y = +coords[0]; } + } else if (Array.isArray(universal)) { + if (universal.length === 2) { + x = +universal[0]; + y = +universal[1]; + } else if (universal.length === 1) { + x = y = +universal[0]; + } } axisX = +axisX; @@ -51,8 +58,8 @@ export function props2transform(props) { const skew = universal2axis(props.skew, props.skewX, props.skewY); const translate = universal2axis( props.translate, - props.translateX == null ? props.x || 0 : props.translateX, - props.translateY == null ? props.y || 0 : props.translateY, + props.translateX || props.x, + props.translateY || props.y, ); return { From c343229441ade3e3422aba00de80b4ca50b8bbe1 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 23 Apr 2019 00:51:52 +0300 Subject: [PATCH 70/71] [iOS] fix handling kerning, wordSpacing and letterSpacing when Number https://github.com/react-native-community/react-native-svg/issues/568#issuecomment-485518338 --- ios/Text/RNSVGFontData.m | 47 ++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/ios/Text/RNSVGFontData.m b/ios/Text/RNSVGFontData.m index 0d902ac0..9b9d9805 100644 --- a/ios/Text/RNSVGFontData.m +++ b/ios/Text/RNSVGFontData.m @@ -84,25 +84,40 @@ RNSVGFontData *RNSVGFontData_Defaults; NSString* decoration = [font objectForKey:TEXT_DECORATION]; data->textDecoration = decoration ? RNSVGTextDecorationFromString(decoration) : parent->textDecoration; - NSString* kerning = [font objectForKey:KERNING]; - data->manualKerning = (kerning || parent->manualKerning ); CGFloat fontSize = data->fontSize; - data->kerning = kerning ? - [RNSVGFontData toAbsoluteWithNSString:kerning - fontSize:fontSize] - : parent->kerning; + id kerning = [font objectForKey:KERNING]; + data->manualKerning = (kerning || parent->manualKerning ); + if ([kerning isKindOfClass:NSNumber.class]) { + NSNumber* kern = kerning; + data->kerning = (CGFloat)[kern doubleValue]; + } else { + data->kerning = kerning ? + [RNSVGFontData toAbsoluteWithNSString:kerning + fontSize:fontSize] + : parent->kerning; + } - NSString* wordSpacing = [font objectForKey:WORD_SPACING]; - data->wordSpacing = wordSpacing ? - [RNSVGFontData toAbsoluteWithNSString:wordSpacing - fontSize:fontSize] - : parent->wordSpacing; + id wordSpacing = [font objectForKey:WORD_SPACING]; + if ([wordSpacing isKindOfClass:NSNumber.class]) { + NSNumber* ws = wordSpacing; + data->wordSpacing = (CGFloat)[ws doubleValue]; + } else { + data->wordSpacing = wordSpacing ? + [RNSVGFontData toAbsoluteWithNSString:wordSpacing + fontSize:fontSize] + : parent->wordSpacing; + } - NSString* letterSpacing = [font objectForKey:LETTER_SPACING]; - data->letterSpacing = letterSpacing ? - [RNSVGFontData toAbsoluteWithNSString:letterSpacing - fontSize:fontSize] - : parent->letterSpacing; + id letterSpacing = [font objectForKey:LETTER_SPACING]; + if ([letterSpacing isKindOfClass:NSNumber.class]) { + NSNumber* ls = letterSpacing; + data->wordSpacing = (CGFloat)[ls doubleValue]; + } else { + data->letterSpacing = letterSpacing ? + [RNSVGFontData toAbsoluteWithNSString:letterSpacing + fontSize:fontSize] + : parent->letterSpacing; + } return data; } From 5bf58672f52abaf463eac6c2fbaefab96a8df55d Mon Sep 17 00:00:00 2001 From: Robert Barclay Date: Fri, 26 Apr 2019 08:02:07 -0600 Subject: [PATCH 71/71] Added missing header to the RNSVGImage.m file to fix a build error when when importing as a cocoapod --- ios/Elements/RNSVGImage.m | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m index 3d8dc444..6d7bddf8 100644 --- a/ios/Elements/RNSVGImage.m +++ b/ios/Elements/RNSVGImage.m @@ -10,6 +10,7 @@ #import "RCTConvert+RNSVG.h" #import #import +#import #import #import "RNSVGViewBox.h"