diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java index 28343363..de9214f7 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java @@ -35,6 +35,7 @@ public class RNSVGRenderableViewManager extends ViewGroupManager { /* package */ static final String CLASS_VIEW_BOX = "RNSVGViewBox"; /* package */ static final String CLASS_LINEAR_GRADIENT = "RNSVGLinearGradient"; /* package */ static final String CLASS_RADIAL_GRADIENT = "RNSVGRadialGradient"; + /* package */ static final String CLASS_SPAN = "RNSVGSpan"; private final String mClassName; @@ -96,6 +97,10 @@ public class RNSVGRenderableViewManager extends ViewGroupManager { return new RNSVGRenderableViewManager(CLASS_RADIAL_GRADIENT); } + public static RNSVGRenderableViewManager createRNSVGSpanManager() { + return new RNSVGRenderableViewManager(CLASS_SPAN); + } + private RNSVGRenderableViewManager(String className) { mClassName = className; } @@ -150,6 +155,9 @@ public class RNSVGRenderableViewManager extends ViewGroupManager { case CLASS_RADIAL_GRADIENT: mVirtualNode = new RNSVGRadialGradientShadowNode(); break; + case CLASS_SPAN: + mVirtualNode = new RNSVGSpanShadowNode(); + break; default: throw new IllegalStateException("Unexpected type " + mClassName); } @@ -189,6 +197,8 @@ public class RNSVGRenderableViewManager extends ViewGroupManager { return RNSVGLinearGradientShadowNode.class; case CLASS_RADIAL_GRADIENT: return RNSVGRadialGradientShadowNode.class; + case CLASS_SPAN: + return RNSVGSpanShadowNode.class; default: throw new IllegalStateException("Unexpected type " + mClassName); } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGSpanShadowNode.java new file mode 100644 index 00000000..b6a86704 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/RNSVGSpanShadowNode.java @@ -0,0 +1,155 @@ +/** + * 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.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.util.Log; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.annotations.ReactProp; + +import javax.annotation.Nullable; + +/** + * Shadow node for virtual RNSVGPath view + */ +public class RNSVGSpanShadowNode extends RNSVGPathShadowNode { + + private static final String PROP_FONT_FAMILY = "fontFamily"; + private static final String PROP_FONT_SIZE = "fontSize"; + private static final String PROP_FONT_STYLE = "fontStyle"; + private static final String PROP_FONT_WEIGHT = "fontWeight"; + + private static final int DEFAULT_FONT_SIZE = 12; + + private float mDx; + private float mDy; + private @Nullable String mPx; + private @Nullable String mPy; + private ReadableMap mFont; + private String mContent; + + @ReactProp(name = "dx") + public void setDx(float dx) { + mDx = dx * mScale; + markUpdated(); + } + + @ReactProp(name = "dy") + public void setDy(float dy) { + mDy = dy * mScale; + markUpdated(); + } + + @ReactProp(name = "px") + public void setPx(String px) { + mPx = px; + markUpdated(); + } + + @ReactProp(name = "py") + public void setPy(String py) { + mPy = py; + markUpdated(); + } + + @ReactProp(name = "font") + public void setFont(ReadableMap font) { + mFont = font; + markUpdated(); + } + + @ReactProp(name = "content") + public void setContent(String content) { + mContent = content; + markUpdated(); + } + + @Override + public void draw(Canvas canvas, Paint paint, float opacity) { + mPath = getPath(canvas, paint); + super.draw(canvas, paint, opacity); + } + + @Override + protected Path getPath(Canvas canvas, Paint paint) { + Path path = new Path(); + RNSVGTextShadowNode text = (RNSVGTextShadowNode)getParent(); + + if (text == null) { + return path; + } + + applyTextPropertiesToPaint(paint); + paint.getTextPath(mContent, 0, mContent.length(), 0, 0, path); + + if (!mContent.isEmpty()) { + if (mPx != null) { + text.setOffsetX(PropHelper.fromPercentageToFloat(mPx, mCanvasWidth, 0, mScale), false); + } + + if (mPy != null) { + text.setOffsetY(PropHelper.fromPercentageToFloat(mPy, mCanvasHeight, 0, mScale) - paint.ascent(), false); + } + + text.setOffsetX(mDx, true); + text.setOffsetY(mDy, true); + + Matrix matrix = new Matrix(); + matrix.setTranslate(text.getOffsetX(), text.getOffsetY()); + + text.setOffsetX(getBox(paint).width(), true); + + path.transform(matrix); + } else { + text.setOffsetX(mDx, true); + text.setOffsetY(mDy, true); + } + + return path; + } + + private void applyTextPropertiesToPaint(Paint paint) { + paint.setTextAlign(Paint.Align.LEFT); + + float fontSize = DEFAULT_FONT_SIZE; + if (mFont.hasKey(PROP_FONT_SIZE)) { + fontSize = (float) mFont.getDouble(PROP_FONT_SIZE); + } + paint.setTextSize(fontSize * mScale); + boolean isBold = mFont.hasKey(PROP_FONT_WEIGHT) && "bold".equals(mFont.getString(PROP_FONT_WEIGHT)); + boolean isItalic = mFont.hasKey(PROP_FONT_STYLE) && "italic".equals(mFont.getString(PROP_FONT_STYLE)); + int fontStyle; + if (isBold && isItalic) { + fontStyle = Typeface.BOLD_ITALIC; + } else if (isBold) { + fontStyle = Typeface.BOLD; + } else if (isItalic) { + fontStyle = Typeface.ITALIC; + } else { + fontStyle = Typeface.NORMAL; + } + // NB: if the font family is null / unsupported, the default one will be used + paint.setTypeface(Typeface.create(mFont.getString(PROP_FONT_FAMILY), fontStyle)); + } + + public RectF getBox(Paint paint) { + applyTextPropertiesToPaint(paint); + Rect bound = new Rect(); + paint.getTextBounds(mContent, 0, mContent.length(), bound); + return new RectF(bound); + } +} diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java b/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java index 2053902f..5f027a93 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java @@ -109,13 +109,25 @@ public class RNSVGSvgView extends ViewGroup { } private int getAbsoluteLeft(View view) { + int left = view.getLeft() - view.getScrollX(); + + if (view.getParent() == view.getRootView() || view.getParent() instanceof ReactRootView) { + return left; + } + View parent = (View) view.getParent(); - return view.getLeft() - view.getScrollX() + (parent instanceof ReactRootView ? 0 : getAbsoluteLeft(parent)); + return left + getAbsoluteLeft(parent); } private int getAbsoluteTop(View view) { + int top = view.getTop() - view.getScrollY(); + + if (view.getParent() == view.getRootView() || view.getParent() instanceof ReactRootView) { + return top; + } + View parent = (View) view.getParent(); - return view.getTop() - view.getScrollY() + (parent instanceof ReactRootView ? 0 : getAbsoluteTop(parent)); + return top + getAbsoluteTop(parent); } private void dispatch(MotionEvent ev, TouchEventType type) { diff --git a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java index 45e98774..2d3201e0 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java @@ -19,8 +19,10 @@ import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.graphics.Typeface; import android.text.TextUtils; +import android.util.Log; import android.view.View; import com.facebook.react.bridge.ReadableArray; @@ -30,31 +32,17 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGText view */ -public class RNSVGTextShadowNode extends RNSVGPathShadowNode { +public class RNSVGTextShadowNode extends RNSVGGroupShadowNode { - private static final String PROP_LINES = "lines"; + private float mOffsetX = 0; + private float mOffsetY = 0; - private static final String PROP_FONT = "font"; - private static final String PROP_FONT_FAMILY = "fontFamily"; - private static final String PROP_FONT_SIZE = "fontSize"; - private static final String PROP_FONT_STYLE = "fontStyle"; - private static final String PROP_FONT_WEIGHT = "fontWeight"; - - private static final int DEFAULT_FONT_SIZE = 12; - - private static final int TEXT_ALIGNMENT_CENTER = 2; private static final int TEXT_ALIGNMENT_LEFT = 0; private static final int TEXT_ALIGNMENT_RIGHT = 1; - private @Nullable ReadableMap mFrame; private int mTextAlignment = TEXT_ALIGNMENT_LEFT; private Path mTextPath; - @ReactProp(name = "frame") - public void setFrame(@Nullable ReadableMap frame) { - mFrame = frame; - } - @ReactProp(name = "alignment", defaultInt = TEXT_ALIGNMENT_LEFT) public void setAlignment(int alignment) { mTextAlignment = alignment; @@ -70,173 +58,100 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { - opacity *= mOpacity; - if (opacity > MIN_OPACITY_FOR_DRAW) { - String text = formatText(); - if (text == null) { - return; - } + float shift = getShift(paint); - // only set up the canvas if we have something to draw - int count = saveAndSetupCanvas(canvas); - clip(canvas, paint); - RectF box = getBox(paint, text); + final int count = canvas.save(); - if (setupStrokePaint(paint, opacity, box)) { - drawText(canvas, paint, text); - } - if (setupFillPaint(paint, opacity, box)) { - drawText(canvas, paint, text); - } + Matrix matrix = new Matrix(); + matrix.postTranslate(-shift, 0); + canvas.concat(matrix); + super.draw(canvas, paint, opacity); - restoreCanvas(canvas, count); - markUpdateSeen(); - } - } - - private void drawText(Canvas canvas, Paint paint, String text) { - applyTextPropertiesToPaint(paint); - - if (mTextPath == null) { - canvas.drawText(text, 0, -paint.ascent(), paint); - } else { - Matrix matrix = new Matrix(); - matrix.setTranslate(0, -paint.getTextSize() * 1.1f); - mTextPath.transform(matrix); - canvas.drawTextOnPath(text, mTextPath, 0, -paint.ascent(), paint); - } - } - - private String formatText() { - if (mFrame == null || !mFrame.hasKey(PROP_LINES)) { - return null; - } - - ReadableArray linesProp = mFrame.getArray(PROP_LINES); - if (linesProp == null || linesProp.size() == 0) { - return null; - } - - String[] lines = new String[linesProp.size()]; - for (int i = 0; i < lines.length; i++) { - lines[i] = linesProp.getString(i); - } - return TextUtils.join("\n", lines); - } - - private RectF getBox(Paint paint, String text) { - Rect bound = new Rect(); - paint.getTextBounds(text, 0, text.length(), bound); - return new RectF(bound); - } - - private void applyTextPropertiesToPaint(Paint paint) { - int alignment = mTextAlignment; - switch (alignment) { - case TEXT_ALIGNMENT_LEFT: - paint.setTextAlign(Paint.Align.LEFT); - break; - case TEXT_ALIGNMENT_RIGHT: - paint.setTextAlign(Paint.Align.RIGHT); - break; - case TEXT_ALIGNMENT_CENTER: - paint.setTextAlign(Paint.Align.CENTER); - break; - } - if (mFrame != null) { - if (mFrame.hasKey(PROP_FONT)) { - ReadableMap font = mFrame.getMap(PROP_FONT); - if (font != null) { - float fontSize = DEFAULT_FONT_SIZE; - if (font.hasKey(PROP_FONT_SIZE)) { - fontSize = (float) font.getDouble(PROP_FONT_SIZE); - } - paint.setTextSize(fontSize * mScale); - boolean isBold = - font.hasKey(PROP_FONT_WEIGHT) && "bold".equals(font.getString(PROP_FONT_WEIGHT)); - boolean isItalic = - font.hasKey(PROP_FONT_STYLE) && "italic".equals(font.getString(PROP_FONT_STYLE)); - int fontStyle; - if (isBold && isItalic) { - fontStyle = Typeface.BOLD_ITALIC; - } else if (isBold) { - fontStyle = Typeface.BOLD; - } else if (isItalic) { - fontStyle = Typeface.ITALIC; - } else { - fontStyle = Typeface.NORMAL; - } - // NB: if the font family is null / unsupported, the default one will be used - paint.setTypeface(Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle)); - } - } - } + restoreCanvas(canvas, count); + markUpdateSeen(); } @Override protected Path getPath(Canvas canvas, Paint paint) { - Path path = new Path(); + Path path = getPathFromSuper(canvas, paint); - String text = formatText(); - if (text == null) { - return path; - } - - // TODO: get path while TextPath is set. - if (setupFillPaint(paint, 1.0f, getBox(paint, text))) { - applyTextPropertiesToPaint(paint); - paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path); - path.transform(mMatrix); - } + float shift = getShift(paint); + Matrix matrix = new Matrix(); + matrix.setTranslate(shift, 0); + path.transform(matrix); return path; } - @Override - public int hitTest(Point point, View view, @Nullable Matrix matrix) { - Bitmap bitmap = Bitmap.createBitmap( - mCanvasWidth, - mCanvasHeight, - Bitmap.Config.ARGB_8888); - - Canvas canvas = new Canvas(bitmap); - - if (matrix != null) { - canvas.concat(matrix); - } - - canvas.concat(mMatrix); - - String text = formatText(); - if (text == null) { - return -1; - } - - Paint paint = new Paint(); - clip(canvas, paint); - setHitTestFill(paint); - drawText(canvas, paint, text); - - if (setHitTestStroke(paint)) { - drawText(canvas, paint, text); - } - - canvas.setBitmap(bitmap); - try { - if (bitmap.getPixel(point.x, point.y) != 0) { - return view.getId(); - } - } catch (Exception e) { - return -1; - } finally { - bitmap.recycle(); - } - return -1; + private Path getPathFromSuper(Canvas canvas, Paint paint) { + Path path = super.getPath(canvas, paint); + // reset offsetX and offsetY + mOffsetX = mOffsetY = 0; + return path; } + public void setOffsetX(float x, boolean increase) { + if (increase) { + mOffsetX += x; + } else { + mOffsetX = x; + } + } - @Override - public int hitTest(Point point, View view) { - return this.hitTest(point, view, null); + public void setOffsetY(float y, boolean increase) { + if (increase) { + mOffsetY += y; + } else { + mOffsetY = y; + } + } + + public float getOffsetX() { + return mOffsetX; + } + + public float getOffsetY() { + return mOffsetY; + } + + private float getShift(Paint paint) { + Rect rect = new Rect(); + + for (int i = getChildCount() - 1; i >= 0; i--) { + if (!(getChildAt(i) instanceof RNSVGVirtualNode)) { + continue; + } + + RectF box = ((RNSVGSpanShadowNode) getChildAt(i)).getBox(paint); + + if (rect.top > box.top) { + rect.top = (int)box.top; + } + if (rect.right < box.right) { + rect.right = (int)box.right; + } + if (rect.bottom < box.bottom) { + rect.bottom = (int)box.bottom; + } + if (rect.left > box.left) { + rect.left = (int)box.left; + } + } + + + float width = rect.width(); + float shift; + + switch (mTextAlignment) { + case TEXT_ALIGNMENT_RIGHT: + shift = width; + break; + case TEXT_ALIGNMENT_LEFT: + shift = 0; + break; + default: + shift = width / 2; + } + return shift; } } diff --git a/android/src/main/java/com/horcrux/svg/RNSvgPackage.java b/android/src/main/java/com/horcrux/svg/RNSvgPackage.java index 45d76eb1..299ae8f2 100644 --- a/android/src/main/java/com/horcrux/svg/RNSvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/RNSvgPackage.java @@ -39,6 +39,7 @@ public class RNSvgPackage implements ReactPackage { RNSVGRenderableViewManager.createRNSVGViewBoxViewManager(), RNSVGRenderableViewManager.createRNSVGLinearGradientManager(), RNSVGRenderableViewManager.createRNSVGRadialGradientManager(), + RNSVGRenderableViewManager.createRNSVGSpanManager(), new RNSVGSvgViewManager()); }