From 786b5f237555faa3db478e614f054209904778b0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 00:26:00 +0300 Subject: [PATCH] Implement strokeDasharray correctly. Support units and percentages with whitespace separated and/or (possibly white-space surrounded) comma separated length lists. --- .../main/java/com/horcrux/svg/PropHelper.java | 23 --------------- .../com/horcrux/svg/RenderableShadowNode.java | 23 +++++++++------ lib/extract/extractLengthList.js | 14 ++++++++++ lib/extract/extractStroke.js | 28 +++++++++---------- lib/extract/extractText.js | 26 +++++------------ lib/props.js | 2 +- 6 files changed, 51 insertions(+), 65 deletions(-) create mode 100644 lib/extract/extractLengthList.js diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 4482ea04..3eb1aded 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -20,34 +20,11 @@ import com.facebook.react.bridge.WritableMap; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; - /** * Contains static helper methods for accessing props. */ class PropHelper { - /** - * Converts {@link ReadableArray} to an array of {@code float}. Returns newly created array. - * - * @return a {@code float[]} if converted successfully, or {@code null} if {@param value} was - * {@code null}. - */ - - static - @Nullable - float[] toFloatArray(@Nullable ReadableArray value) { - if (value != null) { - int fromSize = value.size(); - float[] into = new float[fromSize]; - for (int i = 0; i < fromSize; i++) { - into[i] = (float) value.getDouble(i); - } - return into; - } - return null; - } - private static final int inputMatrixDataSize = 6; /** diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 7e5b17f4..886bf557 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -53,7 +53,7 @@ abstract public class RenderableShadowNode extends VirtualNode { private static final int FILL_RULE_NONZERO = 1; public @Nullable ReadableArray mStroke; - public @Nullable float[] mStrokeDasharray; + public @Nullable String[] mStrokeDasharray; public String mStrokeWidth = "1"; public float mStrokeOpacity = 1; @@ -117,12 +117,14 @@ abstract public class RenderableShadowNode extends VirtualNode { @ReactProp(name = "strokeDasharray") public void setStrokeDasharray(@Nullable ReadableArray strokeDasharray) { - - mStrokeDasharray = PropHelper.toFloatArray(strokeDasharray); - if (mStrokeDasharray != null && mStrokeDasharray.length > 0) { - for (int i = 0; i < mStrokeDasharray.length; i++) { - mStrokeDasharray[i] = mStrokeDasharray[i] * mScale; + if (strokeDasharray != null) { + int fromSize = strokeDasharray.size(); + mStrokeDasharray = new String[fromSize]; + for (int i = 0; i < fromSize; i++) { + mStrokeDasharray[i] = strokeDasharray.getString(i); } + } else { + mStrokeDasharray = null; } markUpdated(); } @@ -249,8 +251,13 @@ abstract public class RenderableShadowNode extends VirtualNode { paint.setStrokeWidth((float) strokeWidth); setupPaint(paint, opacity, mStroke); - if (mStrokeDasharray != null && mStrokeDasharray.length > 0) { - paint.setPathEffect(new DashPathEffect(mStrokeDasharray, mStrokeDashoffset)); + if (mStrokeDasharray != null) { + int length = mStrokeDasharray.length; + float[] intervals = new float[length]; + for (int i = 0; i < length; i++) { + intervals[i] = (float)relativeOnOther(mStrokeDasharray[i]); + } + paint.setPathEffect(new DashPathEffect(intervals, mStrokeDashoffset)); } return true; diff --git a/lib/extract/extractLengthList.js b/lib/extract/extractLengthList.js new file mode 100644 index 00000000..610a9fc8 --- /dev/null +++ b/lib/extract/extractLengthList.js @@ -0,0 +1,14 @@ +const spaceReg = /\s+/; +const commaReg = /,/g; + +export default function (lengthList) { + if (typeof lengthList === 'string') { + return lengthList.trim().replace(commaReg, ' ').split(spaceReg); + } else if (typeof lengthList === 'number') { + return [`${lengthList}`]; + } else if (lengthList && typeof lengthList.map === 'function') { + return lengthList.map(d => `${d}`); + } else { + return []; + } +} diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 13b412b9..53bc4069 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -1,8 +1,7 @@ import extractBrush from './extractBrush'; import extractOpacity from './extractOpacity'; import {strokeProps} from '../props'; - -const separator = /\s*,\s*/; +import extractLengthList from "./extractLengthList"; const caps = { butt: 0, @@ -26,21 +25,22 @@ export default function(props, styleProperties) { }); const {stroke} = props; - let strokeWidth = props.strokeWidth; - let strokeDasharray = props.strokeDasharray; + let { + strokeWidth, + strokeDasharray + } = props; if (!strokeDasharray || strokeDasharray === 'none') { strokeDasharray = null; - } else if (typeof strokeDasharray === 'string') { - strokeDasharray = strokeDasharray.split(separator).map(dash => +dash); - } - - // It's a list of comma and/or white space separated s - // and s that specify the lengths of alternating dashes and gaps. - // If an odd number of values is provided, then the list of values is repeated - // to yield an even number of values. Thus, 5,3,2 is equivalent to 5,3,2,5,3,2. - if (strokeDasharray && (strokeDasharray.length % 2) === 1) { - strokeDasharray.concat(strokeDasharray); + } else { + // It's a list of comma and/or white space separated s + // and s that specify the lengths of alternating dashes and gaps. + // If an odd number of values is provided, then the list of values is repeated + // to yield an even number of values. Thus, 5,3,2 is equivalent to 5,3,2,5,3,2. + strokeDasharray = extractLengthList(strokeDasharray); + if (strokeDasharray && (strokeDasharray.length % 2) === 1) { + strokeDasharray.concat(strokeDasharray); + } } if (!strokeWidth || typeof strokeWidth !== 'string') { diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 1f8cce52..be8c3bfc 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -2,12 +2,12 @@ import _ from 'lodash'; //noinspection JSUnresolvedVariable import React, {Children} from 'react'; import TSpan from '../../elements/TSpan'; +import extractLengthList from './extractLengthList'; const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?[ptexm%])*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i; const fontFamilyPrefix = /^[\s"']*/; const fontFamilySuffix = /[\s"']*$/; -const spaceReg = /\s+/; -const commaReg = /,/g; +const commaReg = /\s*,\s*/g; const cachedFontObjectsFromString = {}; @@ -85,18 +85,6 @@ export function extractFont(props) { return _.defaults(ownedFont, font); } -function parseSVGLengthList(delta) { - if (typeof delta === 'string') { - return delta.trim().replace(commaReg, ' ').split(spaceReg); - } else if (typeof delta === 'number') { - return [delta.toString()]; - } else if (delta && typeof delta.map === 'function') { - return delta.map(d => `${d}`); - } else { - return []; - } -} - export default function(props, container) { const { x, @@ -110,11 +98,11 @@ export default function(props, container) { children } = props; - const positionX = parseSVGLengthList(x); - const positionY = parseSVGLengthList(y); - const deltaX = parseSVGLengthList(dx); - const deltaY = parseSVGLengthList(dy); - rotate = parseSVGLengthList(rotate); + const positionX = extractLengthList(x); + const positionY = extractLengthList(y); + const deltaX = extractLengthList(dx); + const deltaY = extractLengthList(dy); + rotate = extractLengthList(rotate); let content = null; if (typeof children === 'string' || typeof children === 'number') { diff --git a/lib/props.js b/lib/props.js index 7b01046a..b66dba0a 100644 --- a/lib/props.js +++ b/lib/props.js @@ -41,7 +41,7 @@ const strokeProps = { stroke: PropTypes.string, strokeWidth: numberProp, strokeOpacity: numberProp, - strokeDasharray: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.string]), + strokeDasharray: PropTypes.oneOfType([PropTypes.arrayOf(numberProp), PropTypes.string]), strokeDashoffset: numberProp, strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']), strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']),