indices, int topIndex) {
for (int index = topIndex; index >= 0; index--) {
int xIndex = indices.get(index);
indices.set(index, xIndex + 1);
}
}
// https://www.w3.org/TR/SVG11/text.html#FontSizeProperty
/**
* Get font size from context.
*
* ‘font-size’ Value: < absolute-size > | < relative-size > | < length > | < percentage > |
* inherit Initial: medium Applies to: text content elements Inherited: yes, the computed value is
* inherited Percentages: refer to parent element's font size Media: visual Animatable: yes
*
*
This property refers to the size of the font from baseline to baseline when multiple lines
* of text are set solid in a multiline layout environment.
*
*
For SVG, if a < length > is provided without a unit identifier (e.g., an unqualified number
* such as 128), the SVG user agent processes the < length > as a height value in the current user
* coordinate system.
*
*
If a < length > is provided with one of the unit identifiers (e.g., 12pt or 10%), then the
* SVG user agent converts the < length > into a corresponding value in the current user
* coordinate system by applying the rules described in Units.
*
*
Except for any additional information provided in this specification, the normative
* definition of the property is in CSS2 ([CSS2], section 15.2.4).
*/
double getFontSize() {
return mFontSize;
}
double nextX(double advance) {
incrementIndices(mXIndices, mXsIndex);
int nextIndex = mXIndex + 1;
if (nextIndex < mXs.length) {
mDX = 0;
mXIndex = nextIndex;
SVGLength string = mXs[nextIndex];
mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize);
}
mX += advance;
return mX;
}
double nextY() {
incrementIndices(mYIndices, mYsIndex);
int nextIndex = mYIndex + 1;
if (nextIndex < mYs.length) {
mDY = 0;
mYIndex = nextIndex;
SVGLength string = mYs[nextIndex];
mY = PropHelper.fromRelative(string, mHeight, 0, mScale, mFontSize);
}
return mY;
}
double nextDeltaX() {
incrementIndices(mDXIndices, mDXsIndex);
int nextIndex = mDXIndex + 1;
if (nextIndex < mDXs.length) {
mDXIndex = nextIndex;
SVGLength string = mDXs[nextIndex];
double val = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize);
mDX += val;
}
return mDX;
}
double nextDeltaY() {
incrementIndices(mDYIndices, mDYsIndex);
int nextIndex = mDYIndex + 1;
if (nextIndex < mDYs.length) {
mDYIndex = nextIndex;
SVGLength string = mDYs[nextIndex];
double val = PropHelper.fromRelative(string, mHeight, 0, mScale, mFontSize);
mDY += val;
}
return mDY;
}
double nextRotation() {
incrementIndices(mRIndices, mRsIndex);
mRIndex = Math.min(mRIndex + 1, mRs.length - 1);
return mRs[mRIndex];
}
float getWidth() {
return mWidth;
}
float getHeight() {
return mHeight;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/GlyphPathBag.java
================================================
package com.horcrux.svg;
import android.graphics.Paint;
import android.graphics.Path;
import java.util.ArrayList;
class GlyphPathBag {
private final ArrayList paths = new ArrayList<>();
private final int[][] data = new int[256][];
private final Paint paint;
GlyphPathBag(Paint paint) {
this.paint = paint;
// Make indexed-by-one, to allow zero to represent non-cached
paths.add(new Path());
}
Path getOrCreateAndCache(char ch, String current) {
int index = getIndex(ch);
Path cached;
if (index != 0) {
cached = paths.get(index);
} else {
cached = new Path();
paint.getTextPath(current, 0, 1, 0, 0, cached);
int[] bin = data[ch >> 8];
if (bin == null) {
bin = data[ch >> 8] = new int[256];
}
bin[ch & 0xFF] = paths.size();
paths.add(cached);
}
Path glyph = new Path();
glyph.addPath(cached);
return glyph;
}
private int getIndex(char ch) {
int[] bin = data[ch >> 8];
if (bin == null) return 0;
return bin[ch & 0xFF];
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/GroupView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.view.View;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import java.util.ArrayList;
import javax.annotation.Nullable;
@SuppressLint("ViewConstructor")
class GroupView extends RenderableView {
@Nullable ReadableMap mFont;
private GlyphContext mGlyphContext;
private Bitmap mLayerBitmap;
private Canvas mLayerCanvas;
private final Paint mLayerPaint;
public GroupView(ReactContext reactContext) {
super(reactContext);
mLayerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
public void setFont(Dynamic dynamic) {
if (dynamic.getType() == ReadableType.Map) {
mFont = dynamic.asMap();
} else {
mFont = null;
}
invalidate();
}
public void setFont(@Nullable ReadableMap font) {
mFont = font;
invalidate();
}
void setupGlyphContext(Canvas canvas) {
RectF clipBounds = new RectF(canvas.getClipBounds());
if (mMatrix != null) {
mMatrix.mapRect(clipBounds);
}
mGlyphContext = new GlyphContext(mScale, clipBounds.width(), clipBounds.height());
}
GlyphContext getGlyphContext() {
return mGlyphContext;
}
private static T requireNonNull(T obj) {
if (obj == null) throw new NullPointerException();
return obj;
}
GlyphContext getTextRootGlyphContext() {
return requireNonNull(getTextRoot()).getGlyphContext();
}
void pushGlyphContext() {
getTextRootGlyphContext().pushContext(this, mFont);
}
void popGlyphContext() {
getTextRootGlyphContext().popContext();
}
void draw(final Canvas canvas, final Paint paint, final float opacity) {
setupGlyphContext(canvas);
clip(canvas, paint);
drawGroup(canvas, paint, opacity);
renderMarkers(canvas, paint, opacity);
}
void drawGroup(final Canvas canvas, final Paint paint, final float opacity) {
pushGlyphContext();
final SvgView svg = getSvgView();
final GroupView self = this;
final RectF groupRect = new RectF();
if (mOpacity != 1) {
if (mLayerBitmap == null) {
mLayerBitmap =
Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
mLayerCanvas = new Canvas(mLayerBitmap);
} else {
mLayerBitmap.recycle();
mLayerBitmap =
Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
mLayerCanvas.setBitmap(mLayerBitmap);
}
// Copy current matrix from original canvas
mLayerCanvas.save();
mLayerCanvas.setMatrix(canvas.getMatrix());
} else {
mLayerCanvas = canvas;
}
elements = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof MaskView || child instanceof ClipPathView) {
((RenderableView) child).mergeProperties(self);
continue;
}
if (child instanceof VirtualView) {
VirtualView node = ((VirtualView) child);
if ("none".equals(node.mDisplay)) {
continue;
}
if (node instanceof RenderableView) {
((RenderableView) node).mergeProperties(self);
}
int count = node.saveAndSetupCanvas(mLayerCanvas, mCTM);
node.render(mLayerCanvas, paint, opacity);
RectF r = node.getClientRect();
if (r != null) {
groupRect.union(r);
}
node.restoreCanvas(mLayerCanvas, count);
if (node instanceof RenderableView) {
((RenderableView) node).resetProperties();
}
if (node.isResponsible()) {
svg.enableTouchEvents();
}
if (node.elements != null) {
elements.addAll(node.elements);
}
} else if (child instanceof SvgView) {
SvgView svgView = (SvgView) child;
// Merge properties with inner Svg element.
if (svgView.getChildCount() > 0) {
View viewNode = svgView.getChildAt(0);
if (viewNode instanceof GroupView) {
((GroupView) viewNode).mergeProperties(self);
}
}
svgView.drawChildren(canvas);
if (svgView.isResponsible()) {
svg.enableTouchEvents();
}
}
}
if (mOpacity != 1) {
// Restore copied canvas and temporary reset original canvas matrix to draw bitmap 1:1
mLayerCanvas.restore();
int saveCount = canvas.save();
canvas.setMatrix(null);
mLayerPaint.setAlpha((int) (mOpacity * 255));
if (mLayerBitmap != null) {
canvas.drawBitmap(mLayerBitmap, 0, 0, mLayerPaint);
}
canvas.restoreToCount(saveCount);
}
this.setClientRect(groupRect);
popGlyphContext();
}
void drawPath(Canvas canvas, Paint paint, float opacity) {
super.draw(canvas, paint, opacity);
}
@Override
Path getPath(final Canvas canvas, final Paint paint) {
if (mPath != null) {
return mPath;
}
mPath = new Path();
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof MaskView) {
continue;
}
if (node instanceof VirtualView) {
VirtualView n = (VirtualView) node;
Matrix transform = n.mMatrix;
Path path = n.getPath(canvas, paint);
if (path != null) {
mPath.addPath(path, transform);
}
}
}
return mPath;
}
Path getPath(final Canvas canvas, final Paint paint, final Region.Op op) {
final Path path = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final Path.Op pop = Path.Op.valueOf(op.name());
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof MaskView) {
continue;
}
if (node instanceof VirtualView) {
VirtualView n = (VirtualView) node;
Matrix transform = n.mMatrix;
Path p2;
if (n instanceof GroupView) {
p2 = ((GroupView) n).getPath(canvas, paint, op);
} else {
p2 = n.getPath(canvas, paint);
}
p2.transform(transform);
path.op(p2, pop);
}
}
} else {
Rect clipBounds = canvas.getClipBounds();
final Region bounds = new Region(clipBounds);
final Region r = new Region();
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof MaskView) {
continue;
}
if (node instanceof VirtualView) {
VirtualView n = (VirtualView) node;
Matrix transform = n.mMatrix;
Path p2;
if (n instanceof GroupView) {
p2 = ((GroupView) n).getPath(canvas, paint, op);
} else {
p2 = n.getPath(canvas, paint);
}
if (transform != null) {
p2.transform(transform);
}
Region r2 = new Region();
r2.setPath(p2, bounds);
r.op(r2, op);
}
}
path.addPath(r.getBoundaryPath());
}
return path;
}
@Override
int hitTest(final float[] src) {
if (!mInvertible) {
return -1;
}
float[] dst = new float[2];
mInvMatrix.mapPoints(dst, src);
int x = Math.round(dst[0]);
int y = Math.round(dst[1]);
Path clipPath = getClipPath();
if (clipPath != null) {
if (mClipRegionPath != clipPath) {
mClipRegionPath = clipPath;
mClipBounds = new RectF();
clipPath.computeBounds(mClipBounds, true);
mClipRegion = getRegion(clipPath, mClipBounds);
}
if (!mClipRegion.contains(x, y)) {
return -1;
}
}
for (int i = getChildCount() - 1; i >= 0; i--) {
View child = getChildAt(i);
if (child instanceof VirtualView) {
if (child instanceof MaskView) {
continue;
}
VirtualView node = (VirtualView) child;
int hitChild = node.hitTest(dst);
if (hitChild != -1) {
return (node.isResponsible() || hitChild != child.getId()) ? hitChild : getId();
}
} else if (child instanceof SvgView) {
SvgView node = (SvgView) child;
int hitChild = node.reactTagForTouch(dst[0], dst[1]);
if (hitChild != child.getId()) {
return hitChild;
}
}
}
return -1;
}
void saveDefinition() {
if (mName != null) {
getSvgView().defineTemplate(this, mName);
}
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof VirtualView) {
((VirtualView) node).saveDefinition();
}
}
}
@Override
void resetProperties() {
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof RenderableView) {
((RenderableView) node).resetProperties();
}
}
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/ImageView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.net.Uri;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.logging.FLog;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.imagehelper.ImageSource;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
import com.horcrux.svg.events.SvgLoadEvent;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@SuppressLint("ViewConstructor")
class ImageView extends RenderableView {
private SVGLength mX;
private SVGLength mY;
private SVGLength mW;
private SVGLength mH;
private String uriString;
private int mImageWidth;
private int mImageHeight;
private String mAlign;
private int mMeetOrSlice;
private final AtomicBoolean mLoading = new AtomicBoolean(false);
public ImageView(ReactContext reactContext) {
super(reactContext);
}
public void setX(Dynamic x) {
mX = SVGLength.from(x);
invalidate();
}
public void setY(Dynamic y) {
mY = SVGLength.from(y);
invalidate();
}
public void setWidth(Dynamic width) {
mW = SVGLength.from(width);
invalidate();
}
public void setHeight(Dynamic height) {
mH = SVGLength.from(height);
invalidate();
}
public void setSrc(@Nullable ReadableMap src) {
if (src != null) {
uriString = src.getString("uri");
if (uriString == null || uriString.isEmpty()) {
// TODO: give warning about this
return;
}
if (src.hasKey("width") && src.hasKey("height")) {
mImageWidth = src.getInt("width");
mImageHeight = src.getInt("height");
} else {
mImageWidth = 0;
mImageHeight = 0;
}
Uri mUri = Uri.parse(uriString);
if (mUri.getScheme() == null) {
ResourceDrawableIdHelper.getInstance().getResourceDrawableUri(mContext, uriString);
}
}
}
public void setAlign(String align) {
mAlign = align;
invalidate();
}
public void setMeetOrSlice(int meetOrSlice) {
mMeetOrSlice = meetOrSlice;
invalidate();
}
@Override
void draw(final Canvas canvas, final Paint paint, final float opacity) {
if (!mLoading.get()) {
ImagePipeline imagePipeline = Fresco.getImagePipeline();
ImageSource imageSource = new ImageSource(mContext, uriString);
ImageRequest request = ImageRequest.fromUri(imageSource.getUri());
boolean inMemoryCache = imagePipeline.isInBitmapMemoryCache(request);
if (inMemoryCache) {
tryRenderFromBitmapCache(imagePipeline, request, canvas, paint, opacity * mOpacity);
} else {
loadBitmap(imagePipeline, request);
}
}
}
@Override
Path getPath(Canvas canvas, Paint paint) {
mPath = new Path();
mPath.addRect(getRect(), Path.Direction.CW);
return mPath;
}
private void loadBitmap(final ImagePipeline imagePipeline, final ImageRequest request) {
mLoading.set(true);
final DataSource> dataSource =
imagePipeline.fetchDecodedImage(request, mContext);
BaseBitmapDataSubscriber subscriber =
new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(Bitmap bitmap) {
final EventDispatcher mEventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(mContext, getId());
mEventDispatcher.dispatchEvent(
new SvgLoadEvent(
UIManagerHelper.getSurfaceId(ImageView.this),
getId(),
mContext,
uriString,
bitmap.getWidth(),
bitmap.getHeight()));
mLoading.set(false);
SvgView view = getSvgView();
if (view != null) {
view.invalidate();
}
}
@Override
public void onFailureImpl(DataSource dataSource) {
// No cleanup required here.
// TODO: more details about this failure
mLoading.set(false);
FLog.w(
ReactConstants.TAG,
dataSource.getFailureCause(),
"RNSVG: fetchDecodedImage failed!");
}
};
dataSource.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
}
@Nonnull
private RectF getRect() {
double x = relativeOnWidth(mX);
double y = relativeOnHeight(mY);
double w = relativeOnWidth(mW);
double h = relativeOnHeight(mH);
if (w == 0) {
w = mImageWidth * mScale;
}
if (h == 0) {
h = mImageHeight * mScale;
}
return new RectF((float) x, (float) y, (float) (x + w), (float) (y + h));
}
private void doRender(Canvas canvas, Paint paint, Bitmap bitmap, float opacity) {
if (mImageWidth == 0 || mImageHeight == 0) {
mImageWidth = bitmap.getWidth();
mImageHeight = bitmap.getHeight();
}
RectF renderRect = getRect();
RectF vbRect = new RectF(0, 0, mImageWidth, mImageHeight);
Matrix transform = ViewBox.getTransform(vbRect, renderRect, mAlign, mMeetOrSlice);
transform.mapRect(vbRect);
canvas.clipPath(getPath(canvas, paint));
Path clipPath = getClipPath(canvas, paint);
if (clipPath != null) {
canvas.clipPath(clipPath);
}
Paint alphaPaint = new Paint();
alphaPaint.setAlpha((int) (opacity * 255));
canvas.drawBitmap(bitmap, null, vbRect, alphaPaint);
mCTM.mapRect(vbRect);
this.setClientRect(vbRect);
}
private void tryRenderFromBitmapCache(
ImagePipeline imagePipeline,
ImageRequest request,
Canvas canvas,
Paint paint,
float opacity) {
final DataSource> dataSource =
imagePipeline.fetchImageFromBitmapCache(request, mContext);
try {
final CloseableReference imageReference = dataSource.getResult();
if (imageReference == null) {
return;
}
try {
CloseableImage closeableImage = imageReference.get();
if (!(closeableImage instanceof CloseableBitmap)) {
return;
}
CloseableBitmap closeableBitmap = (CloseableBitmap) closeableImage;
final Bitmap bitmap = closeableBitmap.getUnderlyingBitmap();
if (bitmap == null) {
return;
}
doRender(canvas, paint, bitmap, opacity);
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
CloseableReference.closeSafely(imageReference);
}
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
dataSource.close();
}
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/LineView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import java.util.ArrayList;
@SuppressLint("ViewConstructor")
class LineView extends RenderableView {
private SVGLength mX1;
private SVGLength mY1;
private SVGLength mX2;
private SVGLength mY2;
public LineView(ReactContext reactContext) {
super(reactContext);
}
public void setX1(Dynamic x1) {
mX1 = SVGLength.from(x1);
invalidate();
}
public void setY1(Dynamic y1) {
mY1 = SVGLength.from(y1);
invalidate();
}
public void setX2(Dynamic x2) {
mX2 = SVGLength.from(x2);
invalidate();
}
public void setY2(Dynamic y2) {
mY2 = SVGLength.from(y2);
invalidate();
}
@Override
Path getPath(Canvas canvas, Paint paint) {
Path path = new Path();
double x1 = relativeOnWidth(mX1);
double y1 = relativeOnHeight(mY1);
double x2 = relativeOnWidth(mX2);
double y2 = relativeOnHeight(mY2);
path.moveTo((float) x1, (float) y1);
path.lineTo((float) x2, (float) y2);
elements = new ArrayList<>();
elements.add(
new PathElement(ElementType.kCGPathElementMoveToPoint, new Point[] {new Point(x1, y1)}));
elements.add(
new PathElement(ElementType.kCGPathElementAddLineToPoint, new Point[] {new Point(x2, y2)}));
return path;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/LinearGradientView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants;
import javax.annotation.Nullable;
@SuppressLint("ViewConstructor")
class LinearGradientView extends DefinitionView {
private SVGLength mX1;
private SVGLength mY1;
private SVGLength mX2;
private SVGLength mY2;
private ReadableArray mGradient;
private Brush.BrushUnits mGradientUnits;
private static final float[] sRawMatrix =
new float[] {
1, 0, 0,
0, 1, 0,
0, 0, 1
};
private Matrix mMatrix = null;
public LinearGradientView(ReactContext reactContext) {
super(reactContext);
}
public void setX1(Dynamic x1) {
mX1 = SVGLength.from(x1);
invalidate();
}
public void setY1(Dynamic y1) {
mY1 = SVGLength.from(y1);
invalidate();
}
public void setX2(Dynamic x2) {
mX2 = SVGLength.from(x2);
invalidate();
}
public void setY2(Dynamic y2) {
mY2 = SVGLength.from(y2);
invalidate();
}
public void setGradient(ReadableArray gradient) {
mGradient = gradient;
invalidate();
}
public void setGradientUnits(int gradientUnits) {
switch (gradientUnits) {
case 0:
mGradientUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mGradientUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
invalidate();
}
public void setGradientTransform(@Nullable ReadableArray matrixArray) {
if (matrixArray != null) {
int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale);
if (matrixSize == 6) {
if (mMatrix == null) {
mMatrix = new Matrix();
}
mMatrix.setValues(sRawMatrix);
} else if (matrixSize != -1) {
FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6");
}
} else {
mMatrix = null;
}
invalidate();
}
@Override
void saveDefinition() {
if (mName != null) {
SVGLength[] points = new SVGLength[] {mX1, mY1, mX2, mY2};
Brush brush = new Brush(Brush.BrushType.LINEAR_GRADIENT, points, mGradientUnits);
brush.setGradientColors(mGradient);
if (mMatrix != null) {
brush.setGradientTransform(mMatrix);
}
SvgView svg = getSvgView();
if (mGradientUnits == Brush.BrushUnits.USER_SPACE_ON_USE) {
brush.setUserSpaceBoundingBox(svg.getCanvasBounds());
}
svg.defineBrush(brush, mName);
}
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/MarkerView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
@SuppressLint("ViewConstructor")
class MarkerView extends GroupView {
private SVGLength mRefX;
private SVGLength mRefY;
private SVGLength mMarkerWidth;
private SVGLength mMarkerHeight;
private String mMarkerUnits;
private String mOrient;
private float mMinX;
private float mMinY;
private float mVbWidth;
private float mVbHeight;
String mAlign;
int mMeetOrSlice;
Matrix markerTransform = new Matrix();
public MarkerView(ReactContext reactContext) {
super(reactContext);
}
public void setRefX(Dynamic refX) {
mRefX = SVGLength.from(refX);
invalidate();
}
public void setRefY(Dynamic refY) {
mRefY = SVGLength.from(refY);
invalidate();
}
public void setMarkerWidth(Dynamic markerWidth) {
mMarkerWidth = SVGLength.from(markerWidth);
invalidate();
}
public void setMarkerHeight(Dynamic markerHeight) {
mMarkerHeight = SVGLength.from(markerHeight);
invalidate();
}
public void setMarkerUnits(String markerUnits) {
mMarkerUnits = markerUnits;
invalidate();
}
public void setOrient(String orient) {
mOrient = orient;
invalidate();
}
public void setMinX(float minX) {
mMinX = minX;
invalidate();
}
public void setMinY(float minY) {
mMinY = minY;
invalidate();
}
public void setVbWidth(float vbWidth) {
mVbWidth = vbWidth;
invalidate();
}
public void setVbHeight(float vbHeight) {
mVbHeight = vbHeight;
invalidate();
}
public void setAlign(String align) {
mAlign = align;
invalidate();
}
public void setMeetOrSlice(int meetOrSlice) {
mMeetOrSlice = meetOrSlice;
invalidate();
}
@Override
void saveDefinition() {
if (mName != null) {
SvgView svg = getSvgView();
svg.defineMarker(this, mName);
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof VirtualView) {
((VirtualView) node).saveDefinition();
}
}
}
}
void renderMarker(
Canvas canvas, Paint paint, float opacity, RNSVGMarkerPosition position, float strokeWidth) {
int count = saveAndSetupCanvas(canvas, mCTM);
markerTransform.reset();
Point origin = position.origin;
markerTransform.setTranslate((float) origin.x, (float) origin.y);
double markerAngle = "auto".equals(mOrient) ? -1 : Double.parseDouble(mOrient);
float degrees = 180 + (float) (markerAngle == -1 ? position.angle : markerAngle);
markerTransform.preRotate(degrees);
boolean useStrokeWidth = "strokeWidth".equals(mMarkerUnits);
if (useStrokeWidth) {
markerTransform.preScale(strokeWidth / mScale, strokeWidth / mScale);
}
double width = relativeOnWidth(mMarkerWidth);
double height = relativeOnHeight(mMarkerHeight);
RectF eRect = new RectF(0, 0, (float) width, (float) height);
if (mAlign != null) {
RectF vbRect =
new RectF(
mMinX * mScale,
mMinY * mScale,
(mMinX + mVbWidth) * mScale,
(mMinY + mVbHeight) * mScale);
Matrix viewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice);
float[] values = new float[9];
viewBoxMatrix.getValues(values);
markerTransform.preScale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
}
double x = relativeOnWidth(mRefX);
double y = relativeOnHeight(mRefY);
markerTransform.preTranslate((float) -x, (float) -y);
canvas.concat(markerTransform);
drawGroup(canvas, paint, opacity);
restoreCanvas(canvas, count);
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/MaskView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
@SuppressLint("ViewConstructor")
class MaskView extends GroupView {
SVGLength mX;
SVGLength mY;
SVGLength mW;
SVGLength mH;
// TODO implement proper support for units
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private Brush.BrushUnits mMaskUnits;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private Brush.BrushUnits mMaskContentUnits;
MaskType mMaskType;
enum MaskType {
LUMINANCE,
ALPHA
}
public MaskView(ReactContext reactContext) {
super(reactContext);
}
public void setX(Dynamic x) {
mX = SVGLength.from(x);
invalidate();
}
public void setY(Dynamic y) {
mY = SVGLength.from(y);
invalidate();
}
public void setWidth(Dynamic width) {
mW = SVGLength.from(width);
invalidate();
}
public void setHeight(Dynamic height) {
mH = SVGLength.from(height);
invalidate();
}
public Brush.BrushUnits getMaskUnits() {
return mMaskUnits;
}
public void setMaskUnits(int maskUnits) {
switch (maskUnits) {
case 0:
mMaskUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mMaskUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
invalidate();
}
public void setMaskContentUnits(int maskContentUnits) {
switch (maskContentUnits) {
case 0:
mMaskContentUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mMaskContentUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
invalidate();
}
public MaskType getMaskType() {
return mMaskType;
}
public void setMaskType(int maskType) {
switch (maskType) {
case 0:
mMaskType = MaskType.LUMINANCE;
break;
case 1:
mMaskType = MaskType.ALPHA;
break;
}
invalidate();
}
@Override
void saveDefinition() {
if (mName != null) {
SvgView svg = getSvgView();
svg.defineMask(this, mName);
}
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/PathParser.java
================================================
package com.horcrux.svg;
import android.graphics.Path;
import android.graphics.RectF;
import java.util.ArrayList;
class PathElement {
ElementType type;
Point[] points;
PathElement(ElementType type, Point[] points) {
this.type = type;
this.points = points;
}
}
class PathParser {
static float mScale;
private static int i;
private static int l;
private static String s;
private static Path mPath;
static ArrayList elements;
private static float mPenX;
private static float mPenY;
private static float mPivotX;
private static float mPivotY;
private static float mPenDownX;
private static float mPenDownY;
private static boolean mPenDown;
static Path parse(String d) {
elements = new ArrayList<>();
mPath = new Path();
if (d == null) {
return mPath;
}
char prev_cmd = ' ';
l = d.length();
s = d;
i = 0;
mPenX = 0f;
mPenY = 0f;
mPivotX = 0f;
mPivotY = 0f;
mPenDownX = 0f;
mPenDownY = 0f;
mPenDown = false;
while (i < l) {
skip_spaces();
if (i >= l) {
break;
}
boolean has_prev_cmd = prev_cmd != ' ';
char first_char = s.charAt(i);
if (!has_prev_cmd && first_char != 'M' && first_char != 'm') {
// The first segment must be a MoveTo.
throw new IllegalArgumentException(
String.format("Unexpected character '%c' (i=%d, s=%s)", first_char, i, s));
}
// TODO: simplify
boolean is_implicit_move_to;
char cmd;
if (is_cmd(first_char)) {
is_implicit_move_to = false;
cmd = first_char;
i += 1;
} else if (is_number_start(first_char) && has_prev_cmd) {
if (prev_cmd == 'Z' || prev_cmd == 'z') {
// ClosePath cannot be followed by a number.
throw new IllegalArgumentException(
String.format("Unexpected number after 'z' (s=%s)", s));
}
if (prev_cmd == 'M' || prev_cmd == 'm') {
// 'If a moveto is followed by multiple pairs of coordinates,
// the subsequent pairs are treated as implicit lineto commands.'
// So we parse them as LineTo.
is_implicit_move_to = true;
if (is_absolute(prev_cmd)) {
cmd = 'L';
} else {
cmd = 'l';
}
} else {
is_implicit_move_to = false;
cmd = prev_cmd;
}
} else {
throw new IllegalArgumentException(
String.format("Unexpected character '%c' (i=%d, s=%s)", first_char, i, s));
}
boolean absolute = is_absolute(cmd);
switch (cmd) {
case 'm':
{
move(parse_list_number(), parse_list_number());
break;
}
case 'M':
{
moveTo(parse_list_number(), parse_list_number());
break;
}
case 'l':
{
line(parse_list_number(), parse_list_number());
break;
}
case 'L':
{
lineTo(parse_list_number(), parse_list_number());
break;
}
case 'h':
{
line(parse_list_number(), 0);
break;
}
case 'H':
{
lineTo(parse_list_number(), mPenY);
break;
}
case 'v':
{
line(0, parse_list_number());
break;
}
case 'V':
{
lineTo(mPenX, parse_list_number());
break;
}
case 'c':
{
curve(
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_list_number());
break;
}
case 'C':
{
curveTo(
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_list_number());
break;
}
case 's':
{
smoothCurve(
parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number());
break;
}
case 'S':
{
smoothCurveTo(
parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number());
break;
}
case 'q':
{
quadraticBezierCurve(
parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number());
break;
}
case 'Q':
{
quadraticBezierCurveTo(
parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number());
break;
}
case 't':
{
smoothQuadraticBezierCurve(parse_list_number(), parse_list_number());
break;
}
case 'T':
{
smoothQuadraticBezierCurveTo(parse_list_number(), parse_list_number());
break;
}
case 'a':
{
arc(
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_flag(),
parse_flag(),
parse_list_number(),
parse_list_number());
break;
}
case 'A':
{
arcTo(
parse_list_number(),
parse_list_number(),
parse_list_number(),
parse_flag(),
parse_flag(),
parse_list_number(),
parse_list_number());
break;
}
case 'z':
case 'Z':
{
close();
break;
}
default:
{
throw new IllegalArgumentException(
String.format("Unexpected comand '%c' (s=%s)", cmd, s));
}
}
if (is_implicit_move_to) {
if (absolute) {
prev_cmd = 'M';
} else {
prev_cmd = 'm';
}
} else {
prev_cmd = cmd;
}
}
return mPath;
}
private static void move(float x, float y) {
moveTo(x + mPenX, y + mPenY);
}
private static void moveTo(float x, float y) {
// FLog.w(ReactConstants.TAG, "move x: " + x + " y: " + y);
mPenDownX = mPivotX = mPenX = x;
mPenDownY = mPivotY = mPenY = y;
mPath.moveTo(x * mScale, y * mScale);
elements.add(
new PathElement(ElementType.kCGPathElementMoveToPoint, new Point[] {new Point(x, y)}));
}
private static void line(float x, float y) {
lineTo(x + mPenX, y + mPenY);
}
private static void lineTo(float x, float y) {
// FLog.w(ReactConstants.TAG, "line x: " + x + " y: " + y);
setPenDown();
mPivotX = mPenX = x;
mPivotY = mPenY = y;
mPath.lineTo(x * mScale, y * mScale);
elements.add(
new PathElement(ElementType.kCGPathElementAddLineToPoint, new Point[] {new Point(x, y)}));
}
private static void curve(float c1x, float c1y, float c2x, float c2y, float ex, float ey) {
curveTo(c1x + mPenX, c1y + mPenY, c2x + mPenX, c2y + mPenY, ex + mPenX, ey + mPenY);
}
private static void curveTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey) {
// FLog.w(ReactConstants.TAG, "curve c1x: " + c1x + " c1y: " + c1y + "ex: " + ex + " ey: " +
// ey);
mPivotX = c2x;
mPivotY = c2y;
cubicTo(c1x, c1y, c2x, c2y, ex, ey);
}
private static void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey) {
setPenDown();
mPenX = ex;
mPenY = ey;
mPath.cubicTo(c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale, ey * mScale);
elements.add(
new PathElement(
ElementType.kCGPathElementAddCurveToPoint,
new Point[] {new Point(c1x, c1y), new Point(c2x, c2y), new Point(ex, ey)}));
}
private static void smoothCurve(float c1x, float c1y, float ex, float ey) {
smoothCurveTo(c1x + mPenX, c1y + mPenY, ex + mPenX, ey + mPenY);
}
private static void smoothCurveTo(float c1x, float c1y, float ex, float ey) {
// FLog.w(ReactConstants.TAG, "smoothcurve c1x: " + c1x + " c1y: " + c1y + "ex: " + ex + " ey: "
// + ey);
float c2x = c1x;
float c2y = c1y;
c1x = (mPenX * 2) - mPivotX;
c1y = (mPenY * 2) - mPivotY;
mPivotX = c2x;
mPivotY = c2y;
cubicTo(c1x, c1y, c2x, c2y, ex, ey);
}
private static void quadraticBezierCurve(float c1x, float c1y, float c2x, float c2y) {
quadraticBezierCurveTo(c1x + mPenX, c1y + mPenY, c2x + mPenX, c2y + mPenY);
}
private static void quadraticBezierCurveTo(float c1x, float c1y, float c2x, float c2y) {
// FLog.w(ReactConstants.TAG, "quad c1x: " + c1x + " c1y: " + c1y + "c2x: " + c2x + " c2y: " +
// c2y);
mPivotX = c1x;
mPivotY = c1y;
float ex = c2x;
float ey = c2y;
c2x = (ex + c1x * 2) / 3;
c2y = (ey + c1y * 2) / 3;
c1x = (mPenX + c1x * 2) / 3;
c1y = (mPenY + c1y * 2) / 3;
cubicTo(c1x, c1y, c2x, c2y, ex, ey);
}
private static void smoothQuadraticBezierCurve(float c1x, float c1y) {
smoothQuadraticBezierCurveTo(c1x + mPenX, c1y + mPenY);
}
private static void smoothQuadraticBezierCurveTo(float c1x, float c1y) {
// FLog.w(ReactConstants.TAG, "smoothquad c1x: " + c1x + " c1y: " + c1y);
float c2x = c1x;
float c2y = c1y;
c1x = (mPenX * 2) - mPivotX;
c1y = (mPenY * 2) - mPivotY;
quadraticBezierCurveTo(c1x, c1y, c2x, c2y);
}
private static void arc(
float rx, float ry, float rotation, boolean outer, boolean clockwise, float x, float y) {
arcTo(rx, ry, rotation, outer, clockwise, x + mPenX, y + mPenY);
}
private static void arcTo(
float rx, float ry, float rotation, boolean outer, boolean clockwise, float x, float y) {
// FLog.w(ReactConstants.TAG, "arc rx: " + rx + " ry: " + ry + " rotation: " + rotation + "
// outer: " + outer + " clockwise: " + clockwise + " x: " + x + " y: " + y);
float tX = mPenX;
float tY = mPenY;
ry = Math.abs(ry == 0 ? (rx == 0 ? (y - tY) : rx) : ry);
rx = Math.abs(rx == 0 ? (x - tX) : rx);
if (rx == 0 || ry == 0 || (x == tX && y == tY)) {
lineTo(x, y);
return;
}
float rad = (float) Math.toRadians(rotation);
float cos = (float) Math.cos(rad);
float sin = (float) Math.sin(rad);
x -= tX;
y -= tY;
// Ellipse Center
float cx = cos * x / 2 + sin * y / 2;
float cy = -sin * x / 2 + cos * y / 2;
float rxry = rx * rx * ry * ry;
float rycx = ry * ry * cx * cx;
float rxcy = rx * rx * cy * cy;
float a = rxry - rxcy - rycx;
if (a < 0) {
a = (float) Math.sqrt(1 - a / rxry);
rx *= a;
ry *= a;
cx = x / 2;
cy = y / 2;
} else {
a = (float) Math.sqrt(a / (rxcy + rycx));
if (outer == clockwise) {
a = -a;
}
float cxd = -a * cy * rx / ry;
float cyd = a * cx * ry / rx;
cx = cos * cxd - sin * cyd + x / 2;
cy = sin * cxd + cos * cyd + y / 2;
}
// Rotation + Scale Transform
float xx = cos / rx;
float yx = sin / rx;
float xy = -sin / ry;
float yy = cos / ry;
// Start and End Angle
float sa = (float) Math.atan2(xy * -cx + yy * -cy, xx * -cx + yx * -cy);
float ea = (float) Math.atan2(xy * (x - cx) + yy * (y - cy), xx * (x - cx) + yx * (y - cy));
cx += tX;
cy += tY;
x += tX;
y += tY;
setPenDown();
mPenX = mPivotX = x;
mPenY = mPivotY = y;
if (rx != ry || rad != 0f) {
arcToBezier(cx, cy, rx, ry, sa, ea, clockwise, rad);
} else {
float start = (float) Math.toDegrees(sa);
float end = (float) Math.toDegrees(ea);
float sweep = Math.abs((start - end) % 360);
if (outer) {
if (sweep < 180) {
sweep = 360 - sweep;
}
} else {
if (sweep > 180) {
sweep = 360 - sweep;
}
}
if (!clockwise) {
sweep = -sweep;
}
RectF oval =
new RectF((cx - rx) * mScale, (cy - rx) * mScale, (cx + rx) * mScale, (cy + rx) * mScale);
mPath.arcTo(oval, start, sweep);
elements.add(
new PathElement(
ElementType.kCGPathElementAddCurveToPoint, new Point[] {new Point(x, y)}));
}
}
private static void close() {
if (mPenDown) {
mPenX = mPenDownX;
mPenY = mPenDownY;
mPenDown = false;
mPath.close();
elements.add(
new PathElement(
ElementType.kCGPathElementCloseSubpath, new Point[] {new Point(mPenX, mPenY)}));
}
}
private static void arcToBezier(
float cx, float cy, float rx, float ry, float sa, float ea, boolean clockwise, float rad) {
// Inverse Rotation + Scale Transform
float cos = (float) Math.cos(rad);
float sin = (float) Math.sin(rad);
float xx = cos * rx;
float yx = -sin * ry;
float xy = sin * rx;
float yy = cos * ry;
// Bezier Curve Approximation
float arc = ea - sa;
if (arc < 0 && clockwise) {
arc += Math.PI * 2;
} else if (arc > 0 && !clockwise) {
arc -= Math.PI * 2;
}
int n = (int) Math.ceil(Math.abs(round(arc / (Math.PI / 2))));
float step = arc / n;
float k = (float) ((4 / 3.0) * Math.tan(step / 4));
float x = (float) Math.cos(sa);
float y = (float) Math.sin(sa);
for (int i = 0; i < n; i++) {
float cp1x = x - k * y;
float cp1y = y + k * x;
sa += step;
x = (float) Math.cos(sa);
y = (float) Math.sin(sa);
float cp2x = x + k * y;
float cp2y = y - k * x;
float c1x = (cx + xx * cp1x + yx * cp1y);
float c1y = (cy + xy * cp1x + yy * cp1y);
float c2x = (cx + xx * cp2x + yx * cp2y);
float c2y = (cy + xy * cp2x + yy * cp2y);
float ex = (cx + xx * x + yx * y);
float ey = (cy + xy * x + yy * y);
mPath.cubicTo(
c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale, ey * mScale);
elements.add(
new PathElement(
ElementType.kCGPathElementAddCurveToPoint,
new Point[] {new Point(c1x, c1y), new Point(c2x, c2y), new Point(ex, ey)}));
}
}
private static void setPenDown() {
if (!mPenDown) {
mPenDownX = mPenX;
mPenDownY = mPenY;
mPenDown = true;
}
}
private static double round(double val) {
double multiplier = Math.pow(10, 4);
return Math.round(val * multiplier) / multiplier;
}
private static void skip_spaces() {
while (i < l && Character.isWhitespace(s.charAt(i))) i++;
}
private static boolean is_cmd(char c) {
switch (c) {
case 'M':
case 'm':
case 'Z':
case 'z':
case 'L':
case 'l':
case 'H':
case 'h':
case 'V':
case 'v':
case 'C':
case 'c':
case 'S':
case 's':
case 'Q':
case 'q':
case 'T':
case 't':
case 'A':
case 'a':
return true;
}
return false;
}
private static boolean is_number_start(char c) {
return (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+';
}
private static boolean is_absolute(char c) {
return Character.isUpperCase(c);
}
// By the SVG spec 'large-arc' and 'sweep' must contain only one char
// and can be written without any separators, e.g.: 10 20 30 01 10 20.
private static boolean parse_flag() {
skip_spaces();
char c = s.charAt(i);
switch (c) {
case '0':
case '1':
{
i += 1;
if (i < l && s.charAt(i) == ',') {
i += 1;
}
skip_spaces();
break;
}
default:
throw new Error(String.format("Unexpected flag '%c' (i=%d, s=%s)", c, i, s));
}
return c == '1';
}
private static float parse_list_number() {
if (i == l) {
throw new Error(String.format("Unexpected end (s=%s)", s));
}
float n = parse_number();
skip_spaces();
parse_list_separator();
return n;
}
private static float parse_number() {
// Strip off leading whitespaces.
skip_spaces();
if (i == l) {
throw new Error(String.format("Unexpected end (s=%s)", s));
}
int start = i;
char c = s.charAt(i);
// Consume sign.
if (c == '-' || c == '+') {
i += 1;
c = s.charAt(i);
}
// Consume integer.
if (c >= '0' && c <= '9') {
skip_digits();
if (i < l) {
c = s.charAt(i);
}
} else if (c != '.') {
throw new IllegalArgumentException(
String.format("Invalid number formating character '%c' (i=%d, s=%s)", c, i, s));
}
// Consume fraction.
if (c == '.') {
i += 1;
skip_digits();
if (i < l) {
c = s.charAt(i);
}
}
if ((c == 'e' || c == 'E') && i + 1 < l) {
char c2 = s.charAt(i + 1);
// Check for `em`/`ex`.
if (c2 != 'm' && c2 != 'x') {
i += 1;
c = s.charAt(i);
if (c == '+' || c == '-') {
i += 1;
skip_digits();
} else if (c >= '0' && c <= '9') {
skip_digits();
} else {
throw new IllegalArgumentException(
String.format("Invalid number formating character '%c' (i=%d, s=%s)", c, i, s));
}
}
}
String num = s.substring(start, i);
float n = Float.parseFloat(num);
// inf, nan, etc. are an error.
if (Float.isInfinite(n) || Float.isNaN(n)) {
throw new IllegalArgumentException(
String.format("Invalid number '%s' (start=%d, i=%d, s=%s)", num, start, i, s));
}
return n;
}
private static void parse_list_separator() {
if (i < l && s.charAt(i) == ',') {
i += 1;
}
}
private static void skip_digits() {
while (i < l && Character.isDigit(s.charAt(i))) i++;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/PathView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import com.facebook.react.bridge.ReactContext;
@SuppressLint("ViewConstructor")
class PathView extends RenderableView {
private Path mPath;
public PathView(ReactContext reactContext) {
super(reactContext);
PathParser.mScale = mScale;
mPath = new Path();
}
public void setD(String d) {
mPath = PathParser.parse(d);
elements = PathParser.elements;
for (PathElement elem : elements) {
for (Point point : elem.points) {
point.x *= mScale;
point.y *= mScale;
}
}
invalidate();
}
@Override
Path getPath(Canvas canvas, Paint paint) {
return mPath;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/PatternView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.RectF;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants;
import javax.annotation.Nullable;
@SuppressLint("ViewConstructor")
class PatternView extends GroupView {
private SVGLength mX;
private SVGLength mY;
private SVGLength mW;
private SVGLength mH;
private Brush.BrushUnits mPatternUnits;
private Brush.BrushUnits mPatternContentUnits;
private float mMinX;
private float mMinY;
private float mVbWidth;
private float mVbHeight;
String mAlign;
int mMeetOrSlice;
private static final float[] sRawMatrix =
new float[] {
1, 0, 0,
0, 1, 0,
0, 0, 1
};
private Matrix mMatrix = null;
public PatternView(ReactContext reactContext) {
super(reactContext);
}
public void setX(Dynamic x) {
mX = SVGLength.from(x);
invalidate();
}
public void setY(Dynamic y) {
mY = SVGLength.from(y);
invalidate();
}
public void setWidth(Dynamic width) {
mW = SVGLength.from(width);
invalidate();
}
public void setHeight(Dynamic height) {
mH = SVGLength.from(height);
invalidate();
}
public void setPatternUnits(int patternUnits) {
switch (patternUnits) {
case 0:
mPatternUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mPatternUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
invalidate();
}
public void setPatternContentUnits(int patternContentUnits) {
switch (patternContentUnits) {
case 0:
mPatternContentUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mPatternContentUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
invalidate();
}
public void setPatternTransform(@Nullable ReadableArray matrixArray) {
if (matrixArray != null) {
int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale);
if (matrixSize == 6) {
if (mMatrix == null) {
mMatrix = new Matrix();
}
mMatrix.setValues(sRawMatrix);
} else if (matrixSize != -1) {
FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6");
}
} else {
mMatrix = null;
}
invalidate();
}
public void setMinX(float minX) {
mMinX = minX;
invalidate();
}
public void setMinY(float minY) {
mMinY = minY;
invalidate();
}
public void setVbWidth(float vbWidth) {
mVbWidth = vbWidth;
invalidate();
}
public void setVbHeight(float vbHeight) {
mVbHeight = vbHeight;
invalidate();
}
public void setAlign(String align) {
mAlign = align;
invalidate();
}
public void setMeetOrSlice(int meetOrSlice) {
mMeetOrSlice = meetOrSlice;
invalidate();
}
RectF getViewBox() {
return new RectF(
mMinX * mScale, mMinY * mScale, (mMinX + mVbWidth) * mScale, (mMinY + mVbHeight) * mScale);
}
@Override
void saveDefinition() {
if (mName != null) {
SVGLength[] points = new SVGLength[] {mX, mY, mW, mH};
Brush brush = new Brush(Brush.BrushType.PATTERN, points, mPatternUnits);
brush.setContentUnits(mPatternContentUnits);
brush.setPattern(this);
if (mMatrix != null) {
brush.setGradientTransform(mMatrix);
}
SvgView svg = getSvgView();
if (mPatternUnits == Brush.BrushUnits.USER_SPACE_ON_USE
|| mPatternContentUnits == Brush.BrushUnits.USER_SPACE_ON_USE) {
brush.setUserSpaceBoundingBox(svg.getCanvasBounds());
}
svg.defineBrush(brush, mName);
}
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/PropHelper.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import com.facebook.react.bridge.ReadableArray;
/** Contains static helper methods for accessing props. */
class PropHelper {
private static final int inputMatrixDataSize = 6;
/**
* Converts given {@link ReadableArray} to a matrix data array, {@code float[6]}. Writes result to
* the array passed in {@param into}. This method will write exactly six items to the output array
* from the input array.
*
* If the input array has a different size, then only the size is returned; Does not check
* output array size. Ensure space for at least six elements.
*
* @param value input array
* @param sRawMatrix output matrix
* @param mScale current resolution scaling
* @return size of input array
*/
static int toMatrixData(ReadableArray value, float[] sRawMatrix, float mScale) {
int fromSize = value.size();
if (fromSize != inputMatrixDataSize) {
return fromSize;
}
sRawMatrix[0] = (float) value.getDouble(0);
sRawMatrix[1] = (float) value.getDouble(2);
sRawMatrix[2] = (float) value.getDouble(4) * mScale;
sRawMatrix[3] = (float) value.getDouble(1);
sRawMatrix[4] = (float) value.getDouble(3);
sRawMatrix[5] = (float) value.getDouble(5) * mScale;
return inputMatrixDataSize;
}
/**
* Converts length string into px / user units in the current user coordinate system
*
* @param length length string
* @param relative relative size for percentages
* @param scale scaling parameter
* @param fontSize current font size
* @return value in the current user coordinate system
*/
static double fromRelative(String length, double relative, double scale, double fontSize) {
/*
TODO list
unit relative to
em font size of the element
ex x-height of the element’s font
ch width of the "0" (ZERO, U+0030) glyph in the element’s font
rem font size of the root element
vw 1% of viewport’s width
vh 1% of viewport’s height
vmin 1% of viewport’s smaller dimension
vmax 1% of viewport’s larger dimension
relative-size [ larger | smaller ]
absolute-size: [ xx-small | x-small | small | medium | large | x-large | xx-large ]
https://www.w3.org/TR/css3-values/#relative-lengths
https://www.w3.org/TR/css3-values/#absolute-lengths
https://drafts.csswg.org/css-cascade-4/#computed-value
https://drafts.csswg.org/css-fonts-3/#propdef-font-size
https://drafts.csswg.org/css2/fonts.html#propdef-font-size
*/
length = length.trim();
int stringLength = length.length();
int percentIndex = stringLength - 1;
if (stringLength == 0 || length.equals("normal")) {
return 0d;
} else if (length.codePointAt(percentIndex) == '%') {
return Double.valueOf(length.substring(0, percentIndex)) / 100 * relative;
} else {
int twoLetterUnitIndex = stringLength - 2;
if (twoLetterUnitIndex > 0) {
String lastTwo = length.substring(twoLetterUnitIndex);
int end = twoLetterUnitIndex;
double unit = 1;
switch (lastTwo) {
case "px":
break;
case "em":
unit = fontSize;
break;
/*
"1pt" equals "1.25px" (and therefore 1.25 user units)
"1pc" equals "15px" (and therefore 15 user units)
"1mm" would be "3.543307px" (3.543307 user units)
"1cm" equals "35.43307px" (and therefore 35.43307 user units)
"1in" equals "90px" (and therefore 90 user units)
*/
case "pt":
unit = 1.25d;
break;
case "pc":
unit = 15;
break;
case "mm":
unit = 3.543307d;
break;
case "cm":
unit = 35.43307d;
break;
case "in":
unit = 90;
break;
default:
end = stringLength;
}
return Double.valueOf(length.substring(0, end)) * unit * scale;
} else {
return Double.valueOf(length) * scale;
}
}
}
/**
* Converts SVGLength into px / user units in the current user coordinate system
*
* @param length length string
* @param relative relative size for percentages
* @param offset offset for all units
* @param scale scaling parameter
* @param fontSize current font size
* @return value in the current user coordinate system
*/
static double fromRelative(
SVGLength length, double relative, double offset, double scale, double fontSize) {
/*
TODO list
unit relative to
em font size of the element
ex x-height of the element’s font
ch width of the "0" (ZERO, U+0030) glyph in the element’s font
rem font size of the root element
vw 1% of viewport’s width
vh 1% of viewport’s height
vmin 1% of viewport’s smaller dimension
vmax 1% of viewport’s larger dimension
relative-size [ larger | smaller ]
absolute-size: [ xx-small | x-small | small | medium | large | x-large | xx-large ]
https://www.w3.org/TR/css3-values/#relative-lengths
https://www.w3.org/TR/css3-values/#absolute-lengths
https://drafts.csswg.org/css-cascade-4/#computed-value
https://drafts.csswg.org/css-fonts-3/#propdef-font-size
https://drafts.csswg.org/css2/fonts.html#propdef-font-size
*/
if (length == null) {
return offset;
}
SVGLength.UnitType unitType = length.unit;
double value = length.value;
double unit = 1;
switch (unitType) {
case NUMBER:
case PX:
break;
case PERCENTAGE:
return value / 100 * relative + offset;
case EMS:
unit = fontSize;
break;
case EXS:
unit = fontSize / 2;
break;
case CM:
unit = 35.43307;
break;
case MM:
unit = 3.543307;
break;
case IN:
unit = 90;
break;
case PT:
unit = 1.25;
break;
case PC:
unit = 15;
break;
default:
case UNKNOWN:
return value * scale + offset;
}
return value * unit * scale + offset;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/RNSVGMarkerPosition.java
================================================
package com.horcrux.svg;
import java.util.ArrayList;
enum RNSVGMarkerType {
kStartMarker,
kMidMarker,
kEndMarker
}
enum ElementType {
kCGPathElementAddCurveToPoint,
kCGPathElementAddQuadCurveToPoint,
kCGPathElementMoveToPoint,
kCGPathElementAddLineToPoint,
kCGPathElementCloseSubpath
}
class Point {
double x;
double y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
}
class SegmentData {
Point start_tangent; // Tangent in the start point of the segment.
Point end_tangent; // Tangent in the end point of the segment.
Point position; // The end point of the segment.
}
class RNSVGMarkerPosition {
private static ArrayList positions_;
private static int element_index_;
private static Point origin_;
private static Point subpath_start_;
private static Point in_slope_;
private static Point out_slope_;
@SuppressWarnings("unused")
private static boolean auto_start_reverse_; // TODO
RNSVGMarkerType type;
Point origin;
double angle;
private RNSVGMarkerPosition(RNSVGMarkerType type, Point origin, double angle) {
this.type = type;
this.origin = origin;
this.angle = angle;
}
static ArrayList fromPath(ArrayList elements) {
positions_ = new ArrayList<>();
element_index_ = 0;
origin_ = new Point(0, 0);
subpath_start_ = new Point(0, 0);
for (PathElement e : elements) {
UpdateFromPathElement(e);
}
PathIsDone();
return positions_;
}
private static void PathIsDone() {
double angle = CurrentAngle(RNSVGMarkerType.kEndMarker);
positions_.add(new RNSVGMarkerPosition(RNSVGMarkerType.kEndMarker, origin_, angle));
}
private static double BisectingAngle(double in_angle, double out_angle) {
// WK193015: Prevent bugs due to angles being non-continuous.
if (Math.abs(in_angle - out_angle) > 180) in_angle += 360;
return (in_angle + out_angle) / 2;
}
private static double rad2deg(double rad) {
double RNSVG_radToDeg = 180 / Math.PI;
return rad * RNSVG_radToDeg;
}
private static double SlopeAngleRadians(Point p) {
return Math.atan2(p.y, p.x);
}
private static double CurrentAngle(RNSVGMarkerType type) {
// For details of this calculation, see:
// http://www.w3.org/TR/SVG/single-page.html#painting-MarkerElement
double in_angle = rad2deg(SlopeAngleRadians(in_slope_));
double out_angle = rad2deg(SlopeAngleRadians(out_slope_));
switch (type) {
case kStartMarker:
if (auto_start_reverse_) out_angle += 180;
return out_angle;
case kMidMarker:
return BisectingAngle(in_angle, out_angle);
case kEndMarker:
return in_angle;
}
return 0;
}
private static Point subtract(Point p1, Point p2) {
return new Point(p2.x - p1.x, p2.y - p1.y);
}
private static boolean isZero(Point p) {
return p.x == 0 && p.y == 0;
}
private static void ComputeQuadTangents(SegmentData data, Point start, Point control, Point end) {
data.start_tangent = subtract(control, start);
data.end_tangent = subtract(end, control);
if (isZero(data.start_tangent)) data.start_tangent = data.end_tangent;
else if (isZero(data.end_tangent)) data.end_tangent = data.start_tangent;
}
private static SegmentData ExtractPathElementFeatures(PathElement element) {
SegmentData data = new SegmentData();
Point[] points = element.points;
switch (element.type) {
case kCGPathElementAddCurveToPoint:
data.position = points[2];
data.start_tangent = subtract(points[0], origin_);
data.end_tangent = subtract(points[2], points[1]);
if (isZero(data.start_tangent)) ComputeQuadTangents(data, points[0], points[1], points[2]);
else if (isZero(data.end_tangent)) ComputeQuadTangents(data, origin_, points[0], points[1]);
break;
case kCGPathElementAddQuadCurveToPoint:
data.position = points[1];
ComputeQuadTangents(data, origin_, points[0], points[1]);
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
data.position = points[0];
data.start_tangent = subtract(data.position, origin_);
data.end_tangent = subtract(data.position, origin_);
break;
case kCGPathElementCloseSubpath:
data.position = subpath_start_;
data.start_tangent = subtract(data.position, origin_);
data.end_tangent = subtract(data.position, origin_);
break;
}
return data;
}
private static void UpdateFromPathElement(PathElement element) {
SegmentData segment_data = ExtractPathElementFeatures(element);
// First update the outgoing slope for the previous element.
out_slope_ = segment_data.start_tangent;
// Record the marker for the previous element.
if (element_index_ > 0) {
RNSVGMarkerType marker_type =
element_index_ == 1 ? RNSVGMarkerType.kStartMarker : RNSVGMarkerType.kMidMarker;
double angle = CurrentAngle(marker_type);
positions_.add(new RNSVGMarkerPosition(marker_type, origin_, angle));
}
// Update the incoming slope for this marker position.
in_slope_ = segment_data.end_tangent;
// Update marker position.
origin_ = segment_data.position;
// If this is a 'move to' segment, save the point for use with 'close'.
if (element.type == ElementType.kCGPathElementMoveToPoint) subpath_start_ = element.points[0];
else if (element.type == ElementType.kCGPathElementCloseSubpath)
subpath_start_ = new Point(0, 0);
++element_index_;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/RNSVGRenderableManager.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.graphics.Region;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.annotation.Nonnull;
@ReactModule(name = RNSVGRenderableManager.NAME)
class RNSVGRenderableManager extends NativeSvgRenderableModuleSpec {
RNSVGRenderableManager(ReactApplicationContext reactContext) {
super(reactContext);
}
public static final String NAME = "RNSVGRenderableModule";
@Nonnull
@Override
public String getName() {
return NAME;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public boolean isPointInFill(Double tag, ReadableMap options) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return false;
}
float scale = svg.mScale;
float x = (float) options.getDouble("x") * scale;
float y = (float) options.getDouble("y") * scale;
int i = svg.hitTest(new float[] {x, y});
return i != -1;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public boolean isPointInStroke(Double tag, ReadableMap options) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return false;
}
try {
svg.getPath(null, null);
} catch (NullPointerException e) {
svg.invalidate();
return false;
}
svg.initBounds();
float scale = svg.mScale;
int x = (int) (options.getDouble("x") * scale);
int y = (int) (options.getDouble("y") * scale);
Region strokeRegion = svg.mStrokeRegion;
return strokeRegion != null && strokeRegion.contains(x, y);
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public double getTotalLength(Double tag) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return 0;
}
Path path;
try {
path = svg.getPath(null, null);
} catch (NullPointerException e) {
svg.invalidate();
return -1;
}
PathMeasure pm = new PathMeasure(path, false);
return pm.getLength() / svg.mScale;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public WritableMap getPointAtLength(Double tag, ReadableMap options) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return Arguments.createMap();
}
Path path;
try {
path = svg.getPath(null, null);
} catch (NullPointerException e) {
svg.invalidate();
return Arguments.createMap();
}
PathMeasure pm = new PathMeasure(path, false);
float length = (float) options.getDouble("length");
float scale = svg.mScale;
float[] pos = new float[2];
float[] tan = new float[2];
float distance = Math.max(0, Math.min(length * scale, pm.getLength()));
pm.getPosTan(distance, pos, tan);
double angle = Math.atan2(tan[1], tan[0]);
WritableMap result = Arguments.createMap();
result.putDouble("x", pos[0] / scale);
result.putDouble("y", pos[1] / scale);
result.putDouble("angle", angle);
return result;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public WritableMap getBBox(Double tag, ReadableMap options) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return Arguments.createMap();
}
boolean fill = options.getBoolean("fill");
boolean stroke = options.getBoolean("stroke");
boolean markers = options.getBoolean("markers");
boolean clipped = options.getBoolean("clipped");
try {
svg.getPath(null, null);
} catch (NullPointerException e) {
svg.invalidate();
return Arguments.createMap();
}
float scale = svg.mScale;
svg.initBounds();
RectF bounds = new RectF();
RectF fillBounds = svg.mFillBounds;
RectF strokeBounds = svg.mStrokeBounds;
RectF markerBounds = svg.mMarkerBounds;
RectF clipBounds = svg.mClipBounds;
if (fill && fillBounds != null) {
bounds.union(fillBounds);
}
if (stroke && strokeBounds != null) {
bounds.union(strokeBounds);
}
if (markers && markerBounds != null) {
bounds.union(markerBounds);
}
if (clipped && clipBounds != null) {
bounds.intersect(clipBounds);
}
WritableMap result = Arguments.createMap();
result.putDouble("x", bounds.left / scale);
result.putDouble("y", bounds.top / scale);
result.putDouble("width", bounds.width() / scale);
result.putDouble("height", bounds.height() / scale);
return result;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public WritableMap getCTM(Double tag) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return Arguments.createMap();
}
float scale = svg.mScale;
Matrix ctm = new Matrix(svg.mCTM);
SvgView svgView = svg.getSvgView();
if (svgView == null) {
throw new RuntimeException("Did not find parent SvgView for view with tag: " + tag);
}
Matrix invViewBoxMatrix = svgView.mInvViewBoxMatrix;
ctm.preConcat(invViewBoxMatrix);
float[] values = new float[9];
ctm.getValues(values);
WritableMap result = Arguments.createMap();
result.putDouble("a", values[Matrix.MSCALE_X]);
result.putDouble("b", values[Matrix.MSKEW_Y]);
result.putDouble("c", values[Matrix.MSKEW_X]);
result.putDouble("d", values[Matrix.MSCALE_Y]);
result.putDouble("e", values[Matrix.MTRANS_X] / scale);
result.putDouble("f", values[Matrix.MTRANS_Y] / scale);
return result;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@Override
public WritableMap getScreenCTM(Double tag) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag.intValue());
if (svg == null) {
return Arguments.createMap();
}
float[] values = new float[9];
svg.mCTM.getValues(values);
float scale = svg.mScale;
WritableMap result = Arguments.createMap();
result.putDouble("a", values[Matrix.MSCALE_X]);
result.putDouble("b", values[Matrix.MSKEW_Y]);
result.putDouble("c", values[Matrix.MSKEW_X]);
result.putDouble("d", values[Matrix.MSCALE_Y]);
result.putDouble("e", values[Matrix.MTRANS_X] / scale);
result.putDouble("f", values[Matrix.MTRANS_Y] / scale);
return result;
}
@ReactMethod
@Override
public void getRawResource(String name, Promise promise) {
try {
ReactApplicationContext context = getReactApplicationContext();
Resources resources = context.getResources();
String packageName = context.getPackageName();
int id = resources.getIdentifier(name, "raw", packageName);
InputStream stream = resources.openRawResource(id);
try {
InputStreamReader reader = new InputStreamReader(stream, UTF_8);
char[] buffer = new char[DEFAULT_BUFFER_SIZE];
StringBuilder builder = new StringBuilder();
int n;
while ((n = reader.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != EOF) {
builder.append(buffer, 0, n);
}
String result = builder.toString();
promise.resolve(result);
} finally {
try {
stream.close();
} catch (IOException ioe) {
// ignore
}
}
} catch (Exception e) {
e.printStackTrace();
promise.reject(e);
}
}
private static final int EOF = -1;
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
}
================================================
FILE: android/src/main/java/com/horcrux/svg/RadialGradientView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants;
import javax.annotation.Nullable;
@SuppressLint("ViewConstructor")
class RadialGradientView extends DefinitionView {
private SVGLength mFx;
private SVGLength mFy;
private SVGLength mRx;
private SVGLength mRy;
private SVGLength mCx;
private SVGLength mCy;
private ReadableArray mGradient;
private Brush.BrushUnits mGradientUnits;
private static final float[] sRawMatrix =
new float[] {
1, 0, 0,
0, 1, 0,
0, 0, 1
};
private Matrix mMatrix = null;
public RadialGradientView(ReactContext reactContext) {
super(reactContext);
}
public void setFx(Dynamic fx) {
mFx = SVGLength.from(fx);
invalidate();
}
public void setFy(Dynamic fy) {
mFy = SVGLength.from(fy);
invalidate();
}
public void setRx(Dynamic rx) {
mRx = SVGLength.from(rx);
invalidate();
}
public void setRy(Dynamic ry) {
mRy = SVGLength.from(ry);
invalidate();
}
public void setCx(Dynamic cx) {
mCx = SVGLength.from(cx);
invalidate();
}
public void setCy(Dynamic cy) {
mCy = SVGLength.from(cy);
invalidate();
}
public void setGradient(ReadableArray gradient) {
mGradient = gradient;
invalidate();
}
public void setGradientUnits(int gradientUnits) {
switch (gradientUnits) {
case 0:
mGradientUnits = Brush.BrushUnits.OBJECT_BOUNDING_BOX;
break;
case 1:
mGradientUnits = Brush.BrushUnits.USER_SPACE_ON_USE;
break;
}
invalidate();
}
public void setGradientTransform(@Nullable ReadableArray matrixArray) {
if (matrixArray != null) {
int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale);
if (matrixSize == 6) {
if (mMatrix == null) {
mMatrix = new Matrix();
}
mMatrix.setValues(sRawMatrix);
} else if (matrixSize != -1) {
FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6");
}
} else {
mMatrix = null;
}
invalidate();
}
@Override
void saveDefinition() {
if (mName != null) {
SVGLength[] points = new SVGLength[] {mFx, mFy, mRx, mRy, mCx, mCy};
Brush brush = new Brush(Brush.BrushType.RADIAL_GRADIENT, points, mGradientUnits);
brush.setGradientColors(mGradient);
if (mMatrix != null) {
brush.setGradientTransform(mMatrix);
}
SvgView svg = getSvgView();
if (mGradientUnits == Brush.BrushUnits.USER_SPACE_ON_USE) {
brush.setUserSpaceBoundingBox(svg.getCanvasBounds());
}
svg.defineBrush(brush, mName);
}
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/RectView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Build;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import java.util.ArrayList;
@SuppressLint("ViewConstructor")
class RectView extends RenderableView {
private SVGLength mX;
private SVGLength mY;
private SVGLength mW;
private SVGLength mH;
private SVGLength mRx;
private SVGLength mRy;
public RectView(ReactContext reactContext) {
super(reactContext);
}
public void setX(Dynamic x) {
mX = SVGLength.from(x);
invalidate();
}
public void setY(Dynamic y) {
mY = SVGLength.from(y);
invalidate();
}
public void setWidth(Dynamic width) {
mW = SVGLength.from(width);
invalidate();
}
public void setHeight(Dynamic height) {
mH = SVGLength.from(height);
invalidate();
}
public void setRx(Dynamic rx) {
mRx = SVGLength.from(rx);
invalidate();
}
public void setRy(Dynamic ry) {
mRy = SVGLength.from(ry);
invalidate();
}
@Override
Path getPath(Canvas canvas, Paint paint) {
Path path = new Path();
double x = relativeOnWidth(mX);
double y = relativeOnHeight(mY);
double w = relativeOnWidth(mW);
double h = relativeOnHeight(mH);
if (mRx != null || mRy != null) {
double rx = 0d;
double ry = 0d;
if (mRx == null) {
ry = relativeOnHeight(mRy);
rx = ry;
} else if (mRy == null) {
rx = relativeOnWidth(mRx);
ry = rx;
} else {
rx = relativeOnWidth(mRx);
ry = relativeOnHeight(mRy);
}
if (rx > w / 2) {
rx = w / 2;
}
if (ry > h / 2) {
ry = h / 2;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
path.addRoundRect(
(float) x,
(float) y,
(float) (x + w),
(float) (y + h),
(float) rx,
(float) ry,
Path.Direction.CW);
} else {
path.addRoundRect(
new RectF((float) x, (float) y, (float) (x + w), (float) (y + h)),
(float) rx,
(float) ry,
Path.Direction.CW);
}
} else {
path.addRect((float) x, (float) y, (float) (x + w), (float) (y + h), Path.Direction.CW);
path.close(); // Ensure isSimplePath = false such that rect doesn't become represented using
// integers
}
elements = new ArrayList<>();
elements.add(
new PathElement(ElementType.kCGPathElementMoveToPoint, new Point[] {new Point(x, y)}));
elements.add(
new PathElement(
ElementType.kCGPathElementAddLineToPoint, new Point[] {new Point(x + w, y)}));
elements.add(
new PathElement(
ElementType.kCGPathElementAddLineToPoint, new Point[] {new Point(x + w, y + h)}));
elements.add(
new PathElement(
ElementType.kCGPathElementAddLineToPoint, new Point[] {new Point(x, y + h)}));
elements.add(
new PathElement(ElementType.kCGPathElementAddLineToPoint, new Point[] {new Point(x, y)}));
return path;
}
}
================================================
FILE: android/src/main/java/com/horcrux/svg/RenderableView.java
================================================
/*
* Copyright (c) 2015-present, Horcrux.
* 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.
*/
package com.horcrux.svg;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.view.ViewParent;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.touch.ReactHitSlopView;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.uimanager.PointerEvents;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@SuppressWarnings({"WeakerAccess", "RedundantSuppression"})
public abstract class RenderableView extends VirtualView implements ReactHitSlopView {
RenderableView(ReactContext reactContext) {
super(reactContext);
setPivotX(0);
setPivotY(0);
}
static RenderableView contextElement;
// strokeLinecap
private static final int CAP_BUTT = 0;
static final int CAP_ROUND = 1;
private static final int CAP_SQUARE = 2;
// strokeLinejoin
private static final int JOIN_BEVEL = 2;
private static final int JOIN_MITER = 0;
static final int JOIN_ROUND = 1;
// fillRule
private static final int FILL_RULE_EVENODD = 0;
static final int FILL_RULE_NONZERO = 1;
// vectorEffect
private static final int VECTOR_EFFECT_DEFAULT = 0;
private static final int VECTOR_EFFECT_NON_SCALING_STROKE = 1;
// static final int VECTOR_EFFECT_INHERIT = 2;
// static final int VECTOR_EFFECT_URI = 3;
/*
Used in mergeProperties, keep public
*/
public int vectorEffect = VECTOR_EFFECT_DEFAULT;
public @Nullable ReadableArray stroke;
public @Nullable SVGLength[] strokeDasharray;
public SVGLength strokeWidth = new SVGLength(1);
public float strokeOpacity = 1;
public float strokeMiterlimit = 4;
public float strokeDashoffset = 0;
public Paint.Cap strokeLinecap = Paint.Cap.BUTT;
public Paint.Join strokeLinejoin = Paint.Join.MITER;
private int mCurrentColor = 0;
public @Nullable ReadableArray fill;
public float fillOpacity = 1;
public Path.FillType fillRule = Path.FillType.WINDING;
/*
End merged properties
*/
private @Nullable ArrayList mLastMergedList;
private @Nullable ArrayList