From 1e6f36053ae044437139f5f31254498d1bd70900 Mon Sep 17 00:00:00 2001 From: fxwan Date: Thu, 27 Apr 2017 10:55:02 +0800 Subject: [PATCH 001/198] Add Basic support to transform props, including translate, rotate, scale, skewX, skewY and matrix. Keep in mind that the current implementation doesn't support 3-arguments rotate(a, x, y) transform yet. --- README.md | 2 +- lib/extract/extractProps.js | 6 +- lib/extract/extractTransform.js | 120 ++++++++++++++++++++++++++++---- 3 files changed, 109 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1b44134ea..08cc53855 100644 --- a/README.md +++ b/README.md @@ -801,7 +801,7 @@ npm i 3. Mask element. 4. Marker element. 5. Load Image from URL. -6. Transform prop support. +6. ~~Transform prop support~~. ### Known issues: 1. Unable to apply focus point of RadialGradient on Android. diff --git a/lib/extract/extractProps.js b/lib/extract/extractProps.js index a0830b953..c632eb63c 100644 --- a/lib/extract/extractProps.js +++ b/lib/extract/extractProps.js @@ -24,11 +24,7 @@ export default function(props, ref) { Object.assign(extractedProps, extractStroke(props, styleProperties)); Object.assign(extractedProps, extractFill(props, styleProperties)); - if (props.transform) { - extractedProps.matrix = extractTransform(props.transform); - } else { - extractedProps.matrix = extractTransform(props); - } + extractedProps.matrix = extractTransform(props); Object.assign(extractedProps, extractResponder(props, ref)); diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index e8b37ef63..ef96a15f5 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -13,18 +13,109 @@ function transformToMatrix(props, transform) { return pooledMatrix.toArray(); } +class TransformParser { + constructor() { + var floating = '(\\-?[\\d\\.e]+)'; + var commaSpace = '\\,?\\s*'; + + this.regex = { + split: /[\s*(\s*|\s*)\s*|\s*,\s*]/, + matrix: new RegExp( + '^matrix\\(' + + floating + commaSpace + + floating + commaSpace + + floating + commaSpace + + floating + commaSpace + + floating + commaSpace + + floating + '\\)$') + }; + } + + parse(transform) { + if (transform) { + var retval = {}; + let transLst = _.filter( + transform.split(this.regex.split), + (ele) => { + return ele !== ""; + } + ); + for (let i=0; i= 0 ; i--) { + matrix[i] = parseFloat(matrix[i]); + }; + } + return matrix; + } +} + +const tp = new TransformParser(); + + function appendTransform(transform) { - pooledMatrix - .appendTransform( - transform.x + transform.originX, - transform.y + transform.originY, - transform.scaleX, transform.scaleY, - transform.rotation, - transform.skewX, - transform.skewY, - transform.originX, - transform.originY - ); + if (transform) { + if (typeof transform === "string") { + var transformParsed = tp.parse(transform); + if (transformParsed.matrix) { + pooledMatrix.append(...transformParsed.matrix); + } + else { + let trans = props2transform(transformParsed); + if (typeof trans !== 'string') { + transform = trans; + } + } + } + if (typeof transform !== "string") { + pooledMatrix + .appendTransform( + transform.x + transform.originX, + transform.y + transform.originY, + transform.scaleX, transform.scaleY, + transform.rotation, + transform.skewX, + transform.skewY, + transform.originX, + transform.originY + ); + } + } } function universal2axis(universal, axisX, axisY, defaultValue) { @@ -57,13 +148,16 @@ function universal2axis(universal, axisX, axisY, defaultValue) { } function props2transform(props) { + if (props && (typeof props === "string")) { + return props; + } let [originX, originY] = universal2axis(props.origin, props.originX, props.originY); let [scaleX, scaleY] = universal2axis(props.scale, props.scaleX, props.scaleY, 1); let [skewX, skewY] = universal2axis(props.skew, props.skewX, props.skewY); let [translateX, translateY] = universal2axis( props.translate, - _.isNil(props.translateX) ? props.x : props.translateX, - _.isNil(props.translateY) ? props.y : props.translateY + _.isNil(props.translateX) ? (props.x || 0): props.translateX, + _.isNil(props.translateY) ? (props.y || 0) : props.translateY ); return { From 6b95d1a33c6b3061f3640aa856b5f3b1856b7c9e Mon Sep 17 00:00:00 2001 From: fxwan Date: Thu, 27 Apr 2017 11:02:49 +0800 Subject: [PATCH 002/198] Fix text position on Y-axis, we should take fontSize in mind to layout the Text/TSpan shapes, SVG coord tes case https://www.w3.org/TR/SVG/images/coords/OrigCoordSys.svg --- lib/extract/extractText.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index b7e628d97..735278bba 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -80,7 +80,7 @@ function parseDelta(delta) { } } -export default function(props, container) { +export default function(props, container, ref) { const { x, y, @@ -96,34 +96,45 @@ export default function(props, container) { let { children } = props; let content = null; + let fontProps = extractFont(props); + if (props.parentFontProps) { + fontProps = Object.assign({}, props.parentFontProps, fontProps); + } if (typeof children === 'string' || typeof children === 'number') { const childrenString = children.toString(); if (container) { - children = {childrenString}; + children = {childrenString}; } else { content = childrenString; children = null; } - } else if (Children.count(children) > 1 || Array.isArray(children)) { + } else if (Children.count(children) >= 1 || Array.isArray(children)) { children = Children.map(children, child => { if (typeof child === 'string' || typeof child === 'number') { - return {child.toString()}; + return {child.toString()}; } else { - return child; + //return child; + return React.cloneElement(child, { + parentFontProps: fontProps + }); } }); } + let posY = null; + if (!_.isNil(y) && fontProps.fontSize) { + posY = (parseFloat(y || 0) - parseFloat(fontProps.fontSize || 0)).toString(); + } return { textAnchor: anchors[textAnchor] || 0, - font: extractFont(props), + font: fontProps, children, content, deltaX, deltaY, startOffset: (startOffset || 0).toString(), positionX: _.isNil(x) ? null : x.toString(), - positionY: _.isNil(y) ? null : y.toString() + positionY: posY }; } From 67e072307c076a48e2eec563396fa189a2f5b54b Mon Sep 17 00:00:00 2001 From: fxwan Date: Thu, 27 Apr 2017 12:06:14 +0800 Subject: [PATCH 003/198] remove the y check --- lib/extract/extractText.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 735278bba..ab0f653c2 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -123,8 +123,10 @@ export default function(props, container, ref) { } let posY = null; - if (!_.isNil(y) && fontProps.fontSize) { + if (fontProps.fontSize) { posY = (parseFloat(y || 0) - parseFloat(fontProps.fontSize || 0)).toString(); + } else if (y) { + posY = y.toString(); } return { textAnchor: anchors[textAnchor] || 0, From b758d77a9807880e46dd868f199ba4400aeb3064 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 5 Jun 2017 23:52:35 +0300 Subject: [PATCH 004/198] Transform fix --- android/src/main/java/com/horcrux/svg/SvgView.java | 5 ----- lib/extract/extractGradient.js | 4 +++- package.json | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index f2ccb1b3f..8170d1eb6 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -47,7 +47,6 @@ public String toString() { private @Nullable Bitmap mBitmap; private EventDispatcher mEventDispatcher; - private long mGestureStartTime = TouchEvent.UNSET; private int mTargetTag; private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper = @@ -125,7 +124,6 @@ private void dispatch(MotionEvent ev, TouchEventType type) { mTargetTag, type, ev, - mGestureStartTime, ev.getX(), ev.getY(), mTouchEventCoalescingKeyHelper)); @@ -134,7 +132,6 @@ private void dispatch(MotionEvent ev, TouchEventType type) { public void handleTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_DOWN) { - mGestureStartTime = ev.getEventTime(); dispatch(ev, TouchEventType.START); } else if (mTargetTag == -1) { // All the subsequent action types are expected to be called after ACTION_DOWN thus target @@ -158,11 +155,9 @@ public void handleTouchEvent(MotionEvent ev) { // Exactly onw of the pointers goes up dispatch(ev, TouchEventType.END); mTargetTag = -1; - mGestureStartTime = TouchEvent.UNSET; } else if (action == MotionEvent.ACTION_CANCEL) { dispatchCancelEvent(ev); mTargetTag = -1; - mGestureStartTime = TouchEvent.UNSET; } else { Log.w( "IGNORE", diff --git a/lib/extract/extractGradient.js b/lib/extract/extractGradient.js index 8667e4883..7befb7fea 100644 --- a/lib/extract/extractGradient.js +++ b/lib/extract/extractGradient.js @@ -46,7 +46,9 @@ export default function(props) { let gradientTransform; - if (props.transform) { + if (props.gradientTransform) { + gradientTransform = extractTransform(props.gradientTransform); + } else if (props.transform) { gradientTransform = extractTransform(props.transform); } else { gradientTransform = extractTransform(props); diff --git a/package.json b/package.json index 644895fad..2c4ad74d5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.1.8", + "version": "5.1.7-transform", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { @@ -22,8 +22,8 @@ "lint": "eslint ./" }, "peerDependencies": { - "react-native": ">=0.44.0", - "react": "16.0.0-alpha.6" + "react-native": ">=0.40.0", + "react": ">=15.4.0" }, "dependencies": { "color": "^0.11.1", From 823a05e72f88cc83ff413c6292ba4579a9fa3ac3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 7 Jun 2017 16:05:41 +0300 Subject: [PATCH 005/198] Implement setGradientTransform --- .../src/main/java/com/horcrux/svg/Brush.java | 36 +++++++++++++----- .../horcrux/svg/LinearGradientShadowNode.java | 38 +++++++++++++++++++ .../horcrux/svg/RadialGradientShadowNode.java | 38 +++++++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/Brush.java b/android/src/main/java/com/horcrux/svg/Brush.java index d0f7237cb..0fe36f121 100644 --- a/android/src/main/java/com/horcrux/svg/Brush.java +++ b/android/src/main/java/com/horcrux/svg/Brush.java @@ -25,6 +25,7 @@ public class Brush { private BrushType mType = BrushType.LINEAR_GRADIENT; private ReadableArray mPoints; private ReadableArray mColors; + private Matrix mMatrix; private boolean mUseObjectBoundingBox; private Rect mUserSpaceBoundingBox; @@ -75,6 +76,10 @@ public void setGradientColors(ReadableArray colors) { mColors = colors; } + public void setGradientTransform(Matrix matrix) { + mMatrix = matrix; + } + private RectF getPaintRect(RectF pathBoundingBox) { RectF rect = mUseObjectBoundingBox ? pathBoundingBox : new RectF(mUserSpaceBoundingBox); float width = rect.width(); @@ -107,15 +112,23 @@ public void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float op float y1 = PropHelper.fromPercentageToFloat(mPoints.getString(1), height, offsetY, scale); float x2 = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, offsetX, scale); float y2 = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, offsetY, scale); - paint.setShader( - new LinearGradient( - x1, - y1, - x2, - y2, - stopsColors, - stops, - Shader.TileMode.CLAMP)); + + Shader linearGradient = new LinearGradient( + x1, + y1, + x2, + y2, + stopsColors, + stops, + Shader.TileMode.CLAMP); + + if (mMatrix != null) { + Matrix m = new Matrix(); + m.preConcat(mMatrix); + linearGradient.setLocalMatrix(m); + } + + paint.setShader(linearGradient); } else if (mType == BrushType.RADIAL_GRADIENT) { float rx = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, 0f, scale); float ry = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, 0f, scale); @@ -135,6 +148,11 @@ public void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float op Matrix radialMatrix = new Matrix(); radialMatrix.preScale(1f, ry / rx); + + if (mMatrix != null) { + radialMatrix.preConcat(mMatrix); + } + radialGradient.setLocalMatrix(radialMatrix); paint.setShader(radialGradient); } else { diff --git a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java index b5c35bcb2..5988b1697 100644 --- a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java @@ -9,11 +9,17 @@ package com.horcrux.svg; +import android.graphics.Matrix; + +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.common.ReactConstants; import com.facebook.react.uimanager.annotations.ReactProp; +import javax.annotation.Nullable; + /** * Shadow node for virtual LinearGradient definition view */ @@ -26,6 +32,10 @@ public class LinearGradientShadowNode extends DefinitionShadowNode { private ReadableArray mGradient; private Brush.BrushUnits mGradientUnits; + private static final float[] sMatrixData = new float[9]; + private static final float[] sRawMatrix = new float[9]; + protected Matrix mMatrix = new Matrix(); + @ReactProp(name = "x1") public void setX1(String x1) { mX1 = x1; @@ -69,6 +79,31 @@ public void setGradientUnits(int gradientUnits) { markUpdated(); } + @ReactProp(name = "gradientTransform") + public void setGradientTransform(@Nullable ReadableArray matrixArray) { + if (matrixArray != null) { + int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + if (matrixSize == 6) { + sRawMatrix[0] = sMatrixData[0]; + sRawMatrix[1] = sMatrixData[2]; + sRawMatrix[2] = sMatrixData[4] * mScale; + sRawMatrix[3] = sMatrixData[1]; + sRawMatrix[4] = sMatrixData[3]; + sRawMatrix[5] = sMatrixData[5] * mScale; + sRawMatrix[6] = 0; + sRawMatrix[7] = 0; + sRawMatrix[8] = 1; + mMatrix.setValues(sRawMatrix); + } else if (matrixSize != -1) { + FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); + } + } else { + mMatrix = null; + } + + markUpdated(); + } + @Override protected void saveDefinition() { if (mName != null) { @@ -80,6 +115,9 @@ protected void saveDefinition() { Brush brush = new Brush(Brush.BrushType.LINEAR_GRADIENT, points, mGradientUnits); brush.setGradientColors(mGradient); + if (mMatrix != null) { + brush.setGradientTransform(mMatrix); + } SvgViewShadowNode svg = getSvgShadowNode(); if (mGradientUnits == Brush.BrushUnits.USER_SPACE_ON_USE) { diff --git a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java index 01dcae31f..44f3c8f9f 100644 --- a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java @@ -9,10 +9,16 @@ package com.horcrux.svg; +import android.graphics.Matrix; + +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.common.ReactConstants; + +import javax.annotation.Nullable; /** * Shadow node for virtual RadialGradient definition view @@ -27,6 +33,10 @@ public class RadialGradientShadowNode extends DefinitionShadowNode { private ReadableArray mGradient; private Brush.BrushUnits mGradientUnits; + private static final float[] sMatrixData = new float[9]; + private static final float[] sRawMatrix = new float[9]; + protected Matrix mMatrix = new Matrix(); + @ReactProp(name = "fx") public void setFx(String fx) { mFx = fx; @@ -82,6 +92,31 @@ public void setGradientUnits(int gradientUnits) { markUpdated(); } + @ReactProp(name = "gradientTransform") + public void setGradientTransform(@Nullable ReadableArray matrixArray) { + if (matrixArray != null) { + int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + if (matrixSize == 6) { + sRawMatrix[0] = sMatrixData[0]; + sRawMatrix[1] = sMatrixData[2]; + sRawMatrix[2] = sMatrixData[4] * mScale; + sRawMatrix[3] = sMatrixData[1]; + sRawMatrix[4] = sMatrixData[3]; + sRawMatrix[5] = sMatrixData[5] * mScale; + sRawMatrix[6] = 0; + sRawMatrix[7] = 0; + sRawMatrix[8] = 1; + mMatrix.setValues(sRawMatrix); + } else if (matrixSize != -1) { + FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); + } + } else { + mMatrix = null; + } + + markUpdated(); + } + @Override protected void saveDefinition() { if (mName != null) { @@ -95,6 +130,9 @@ protected void saveDefinition() { Brush brush = new Brush(Brush.BrushType.RADIAL_GRADIENT, points, mGradientUnits); brush.setGradientColors(mGradient); + if (mMatrix != null) { + brush.setGradientTransform(mMatrix); + } SvgViewShadowNode svg = getSvgShadowNode(); if (mGradientUnits == Brush.BrushUnits.USER_SPACE_ON_USE) { From 28dc02928ebfd3b08a8b7f12935b1a4223e1b06f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 7 Jun 2017 16:51:23 +0300 Subject: [PATCH 006/198] Add upper limit to react-native version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c4ad74d5..68e19ea87 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint": "eslint ./" }, "peerDependencies": { - "react-native": ">=0.40.0", + "react-native": ">=0.40.0 <0.44.0", "react": ">=15.4.0" }, "dependencies": { From d4be0d3a29da1f86f2e5e1b556212db4874bf563 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 7 Jun 2017 17:00:03 +0300 Subject: [PATCH 007/198] Set react-native version to less than 0.43 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68e19ea87..96a256031 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint": "eslint ./" }, "peerDependencies": { - "react-native": ">=0.40.0 <0.44.0", + "react-native": ">=0.40.0 <0.43.0", "react": ">=15.4.0" }, "dependencies": { From 93eaa6bc48c9816e5fa6d256e7b643c9d81a7e1b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 7 Jun 2017 17:05:14 +0300 Subject: [PATCH 008/198] Set react version to <15.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96a256031..97f4ceab5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "peerDependencies": { "react-native": ">=0.40.0 <0.43.0", - "react": ">=15.4.0" + "react": ">=15.4.0 <15.5.0" }, "dependencies": { "color": "^0.11.1", From 77e54cfcfbafe54abd4aefa1dde4ea87e7fb383f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 7 Jun 2017 17:25:22 +0300 Subject: [PATCH 009/198] Fix linting --- elements/TSpan.js | 4 +-- elements/Text.js | 4 +-- elements/TextPath.js | 2 +- lib/extract/extractStroke.js | 2 +- lib/extract/extractTransform.js | 46 ++++++++++++++++----------------- lib/extract/extractViewBox.js | 7 +---- 6 files changed, 30 insertions(+), 35 deletions(-) diff --git a/elements/TSpan.js b/elements/TSpan.js index 153fc4602..3249c3c36 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -26,13 +26,13 @@ export default class extends Shape { return { isInAParentText: true }; - }; + } getContextTypes() { return { isInAParentText: React.PropTypes.bool }; - }; + } setNativeProps = (...args) => { this.root.setNativeProps(...args); diff --git a/elements/Text.js b/elements/Text.js index 7f5399d61..471293bab 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -25,13 +25,13 @@ export default class extends Shape { return { isInAParentText: true }; - }; + } getContextTypes() { return { isInAParentText: React.PropTypes.bool }; - }; + } setNativeProps = (...args) => { this.root.setNativeProps(...args); diff --git a/elements/TextPath.js b/elements/TextPath.js index 095e9dd76..837749948 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -43,7 +43,7 @@ export default class extends Shape { } console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"'); - return {children} + return {children}; } } diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index c82d8155b..270f76db9 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -1,6 +1,6 @@ import extractBrush from './extractBrush'; import extractOpacity from './extractOpacity'; -import {strokeProps} from '../props' +import {strokeProps} from '../props'; const separator = /\s*,\s*/; diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index ef96a15f5..afaa8aeb7 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -37,33 +37,33 @@ class TransformParser { let transLst = _.filter( transform.split(this.regex.split), (ele) => { - return ele !== ""; + return ele !== ''; } ); - for (let i=0; i= 0 ; i--) { + for (var i = matrix.length - 1; i >= 0; i--) { matrix[i] = parseFloat(matrix[i]); - }; + } } return matrix; } @@ -90,7 +90,7 @@ const tp = new TransformParser(); function appendTransform(transform) { if (transform) { - if (typeof transform === "string") { + if (typeof transform === 'string') { var transformParsed = tp.parse(transform); if (transformParsed.matrix) { pooledMatrix.append(...transformParsed.matrix); @@ -102,7 +102,7 @@ function appendTransform(transform) { } } } - if (typeof transform !== "string") { + if (typeof transform !== 'string') { pooledMatrix .appendTransform( transform.x + transform.originX, @@ -148,7 +148,7 @@ function universal2axis(universal, axisX, axisY, defaultValue) { } function props2transform(props) { - if (props && (typeof props === "string")) { + if (props && (typeof props === 'string')) { return props; } let [originX, originY] = universal2axis(props.origin, props.originX, props.originY); @@ -156,7 +156,7 @@ function props2transform(props) { let [skewX, skewY] = universal2axis(props.skew, props.skewX, props.skewY); let [translateX, translateY] = universal2axis( props.translate, - _.isNil(props.translateX) ? (props.x || 0): props.translateX, + _.isNil(props.translateX) ? (props.x || 0) : props.translateX, _.isNil(props.translateY) ? (props.y || 0) : props.translateY ); diff --git a/lib/extract/extractViewBox.js b/lib/extract/extractViewBox.js index 2730e89a0..af2dbaab1 100644 --- a/lib/extract/extractViewBox.js +++ b/lib/extract/extractViewBox.js @@ -1,8 +1,3 @@ - -import React, {Component, PropTypes} from 'react'; -import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; -import {ViewBoxAttributes} from '../attributes'; - const meetOrSliceTypes = { meet: 0, slice: 1, @@ -47,7 +42,7 @@ export default function (props) { vbHeight: +params[3], align, meetOrSlice - } + }; } export { From 49c95f5a302d8b8ab5482a1cbf99ce3ce4f79593 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 8 Jun 2017 17:52:47 +0300 Subject: [PATCH 010/198] Implement transform matrix property for ImageShadowNode Add conditional translation.preConcat(mMatrix) statement --- .../java/com/horcrux/svg/ImageShadowNode.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index 14b470b7e..395edfc5a 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -38,6 +38,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; + /** * Shadow node for virtual Image view */ @@ -53,6 +55,10 @@ public class ImageShadowNode extends RenderableShadowNode { private int mMeetOrSlice; private AtomicBoolean mLoading = new AtomicBoolean(false); + private static final float[] sMatrixData = new float[9]; + private static final float[] sRawMatrix = new float[9]; + protected Matrix mMatrix = new Matrix(); + @ReactProp(name = "x") public void setX(String x) { mX = x; @@ -109,6 +115,32 @@ public void setMeetOrSlice(int meetOrSlice) { markUpdated(); } + @ReactProp(name = "matrix") + public void setMatrix(@Nullable ReadableArray matrixArray) { + if (matrixArray != null) { + int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + if (matrixSize == 6) { + sRawMatrix[0] = sMatrixData[0]; + sRawMatrix[1] = sMatrixData[2]; + sRawMatrix[2] = sMatrixData[4] * mScale; + sRawMatrix[3] = sMatrixData[1]; + sRawMatrix[4] = sMatrixData[3]; + sRawMatrix[5] = sMatrixData[5] * mScale; + sRawMatrix[6] = 0; + sRawMatrix[7] = 0; + sRawMatrix[8] = 1; + mMatrix.setValues(sRawMatrix); + } else if (matrixSize != -1) { + FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); + } + } else { + mMatrix = null; + } + + markUpdated(); + } + + @Override public void draw(final Canvas canvas, final Paint paint, final float opacity) { if (!mLoading.get()) { @@ -187,6 +219,9 @@ private void doRender(Canvas canvas, Paint paint, Bitmap bitmap, float opacity) transform.mapRect(renderRect); Matrix translation = new Matrix(); translation.postTranslate(rectX, rectY); + if (mMatrix != null) { + translation.preConcat(mMatrix); + } translation.mapRect(renderRect); Path clip = new Path(); From 377cb7267f68409d3dd77b72d92f0686bc14833d Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 12 Jun 2017 20:08:52 +0300 Subject: [PATCH 011/198] Change transform PropType to oneOfType([PropTypes.object, PropTypes.string]) --- lib/props.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/props.js b/lib/props.js index 9621f1c63..e074a468e 100644 --- a/lib/props.js +++ b/lib/props.js @@ -73,7 +73,7 @@ const transformProps = { skew: numberProp, skewX: numberProp, skewY: numberProp, - transform: PropTypes.object + transform: PropTypes.oneOfType([PropTypes.object, PropTypes.string]) }; const pathProps = { From e9747bee01d5d5318160742dce486ecf030481a6 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 13 Jun 2017 12:43:32 +0300 Subject: [PATCH 012/198] Revert 6b95d1a --- lib/extract/extractText.js | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index ab0f653c2..b7e628d97 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -80,7 +80,7 @@ function parseDelta(delta) { } } -export default function(props, container, ref) { +export default function(props, container) { const { x, y, @@ -96,47 +96,34 @@ export default function(props, container, ref) { let { children } = props; let content = null; - let fontProps = extractFont(props); - if (props.parentFontProps) { - fontProps = Object.assign({}, props.parentFontProps, fontProps); - } if (typeof children === 'string' || typeof children === 'number') { const childrenString = children.toString(); if (container) { - children = {childrenString}; + children = {childrenString}; } else { content = childrenString; children = null; } - } else if (Children.count(children) >= 1 || Array.isArray(children)) { + } else if (Children.count(children) > 1 || Array.isArray(children)) { children = Children.map(children, child => { if (typeof child === 'string' || typeof child === 'number') { - return {child.toString()}; + return {child.toString()}; } else { - //return child; - return React.cloneElement(child, { - parentFontProps: fontProps - }); + return child; } }); } - let posY = null; - if (fontProps.fontSize) { - posY = (parseFloat(y || 0) - parseFloat(fontProps.fontSize || 0)).toString(); - } else if (y) { - posY = y.toString(); - } return { textAnchor: anchors[textAnchor] || 0, - font: fontProps, + font: extractFont(props), children, content, deltaX, deltaY, startOffset: (startOffset || 0).toString(), positionX: _.isNil(x) ? null : x.toString(), - positionY: posY + positionY: _.isNil(y) ? null : y.toString() }; } From 64ed6861e2408965153c3c998a0f2c995e426aff Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 19 Jun 2017 01:43:04 +0300 Subject: [PATCH 013/198] Improve Text on a path layout rules conformance. Correct glyph point and delta x / y calculation and context handling. Remove incorrect whitespace from getLinePath method call. Correct the y coordinate of the text's origin when rendering glyphs into paths using getTextPath. Remove strange postTranslate transform. https://www.w3.org/TR/SVG11/text.html#TextOnAPath --- .../java/com/horcrux/svg/GlyphContext.java | 29 ++++++++++++------- .../java/com/horcrux/svg/TSpanShadowNode.java | 17 ++++------- .../java/com/horcrux/svg/TextShadowNode.java | 4 +++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 4b5f4df43..cbd0838a9 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -25,6 +25,7 @@ public class GlyphContext { private ArrayList mFontContext; private ArrayList mLocationContext; + private ArrayList mDeltaContext; private ArrayList> mDeltaXContext; private ArrayList> mDeltaYContext; private ArrayList mXContext; @@ -42,6 +43,7 @@ public class GlyphContext { mCurrentLocation = new PointF(); mFontContext = new ArrayList<>(); mLocationContext = new ArrayList<>(); + mDeltaContext = new ArrayList<>(); mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); mXContext = new ArrayList<>(); @@ -59,6 +61,7 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray delt } mLocationContext.add(location); + mDeltaContext.add(new PointF(0, 0)); mFontContext.add(font); mDeltaXContext.add(getFloatArrayListFromReadableArray(deltaX)); mDeltaYContext.add(getFloatArrayListFromReadableArray(deltaY)); @@ -72,6 +75,7 @@ public void popContext() { float x = mXContext.get(mContextLength - 1); mFontContext.remove(mContextLength - 1); mLocationContext.remove(mContextLength - 1); + mDeltaContext.remove(mContextLength - 1); mDeltaXContext.remove(mContextLength - 1); mDeltaYContext.remove(mContextLength - 1); mXContext.remove(mContextLength - 1); @@ -87,21 +91,26 @@ public void popContext() { } public PointF getNextGlyphPoint(float offset, float glyphWidth) { - float dx = getNextDelta(mDeltaXContext); - mCurrentLocation.x += dx; + mXContext.set(mXContext.size() - 1, mCurrentLocation.x + offset + glyphWidth); - float dy = getNextDelta(mDeltaYContext); - mCurrentLocation.y += dy; + return new PointF(mCurrentLocation.x + offset, mCurrentLocation.y); - for (PointF point: mLocationContext) { - point.x += dx; - point.y += dy; - } + } - mXContext.set(mXContext.size() - 1, mCurrentLocation.x + offset + glyphWidth); + public PointF getNextGlyphDelta() { + float dx = getNextDelta(mDeltaXContext); + float dy = getNextDelta(mDeltaYContext); - return new PointF(mCurrentLocation.x + offset, mCurrentLocation.y); + if (mContextLength > 0) { + for (PointF point: mDeltaContext) { + point.x += dx; + point.y += dy; + } + + return mDeltaContext.get(mContextLength - 1); + } + return new PointF(dx, dy); } private float getNextDelta(ArrayList> deltaContext) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 6a9ddc8e8..f0a633a44 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -78,7 +78,7 @@ protected Path getPath(Canvas canvas, Paint paint) { pushGlyphContext(); applyTextPropertiesToPaint(paint); - getLinePath(mContent + " ", paint, path); + getLinePath(mContent + "", paint, path); mCache = path; popGlyphContext(); @@ -99,13 +99,14 @@ private Path getLinePath(String line, Paint paint, Path path) { Path glyph = new Path(); float width = widths[index]; - paint.getTextPath(letter, 0, 1, 0, -paint.ascent(), glyph); + paint.getTextPath(letter, 0, 1, 0, 0, glyph); + PointF glyphDelta = getGlyphDeltaFromContext(); PointF glyphPoint = getGlyphPointFromContext(glyphPosition, width); glyphPosition += width; Matrix matrix = new Matrix(); if (mBezierTransformer != null) { - matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x); + matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x + glyphDelta.x); if (textPathHasReachedEnd()) { break; @@ -113,22 +114,16 @@ private Path getLinePath(String line, Paint paint, Path path) { continue; } + matrix.preTranslate(0, glyphDelta.y); matrix.postTranslate(0, glyphPoint.y); } else { - matrix.setTranslate(glyphPoint.x, glyphPoint.y); + matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); } - glyph.transform(matrix); path.addPath(glyph); } - if (mBezierTransformer != null) { - Matrix matrix = new Matrix(); - matrix.postTranslate(0, paint.ascent() * 1.1f); - path.transform(matrix); - } - return path; } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index b8be10d60..a38c855ab 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -200,6 +200,10 @@ protected PointF getGlyphPointFromContext(float offset, float glyphWidth) { return getTextRoot().getGlyphContext().getNextGlyphPoint(offset, glyphWidth); } + protected PointF getGlyphDeltaFromContext() { + return getTextRoot().getGlyphContext().getNextGlyphDelta(); + } + private Matrix getAlignMatrix(Path path) { RectF box = new RectF(); path.computeBounds(box, true); From 1e8ce2be58e3c6f378cf1d23f6023fdca139d5d8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 19 Jun 2017 03:09:16 +0300 Subject: [PATCH 014/198] Fix accumulation of dx/dy and push/pop of y in GlyphContext. --- .../main/java/com/horcrux/svg/GlyphContext.java | 16 ++++++++++++++-- .../java/com/horcrux/svg/TSpanShadowNode.java | 3 +-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index cbd0838a9..8750ec479 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -29,7 +29,9 @@ public class GlyphContext { private ArrayList> mDeltaXContext; private ArrayList> mDeltaYContext; private ArrayList mXContext; + private ArrayList mYContext; private @Nonnull PointF mCurrentLocation; + private @Nonnull PointF mCurrentDelta; private float mScale; private float mWidth; private float mHeight; @@ -41,12 +43,14 @@ public class GlyphContext { mWidth = width; mHeight = height; mCurrentLocation = new PointF(); + mCurrentDelta = new PointF(); mFontContext = new ArrayList<>(); mLocationContext = new ArrayList<>(); mDeltaContext = new ArrayList<>(); mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); mXContext = new ArrayList<>(); + mYContext = new ArrayList<>(); } public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { @@ -61,40 +65,48 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray delt } mLocationContext.add(location); - mDeltaContext.add(new PointF(0, 0)); + mDeltaContext.add(mCurrentDelta); mFontContext.add(font); mDeltaXContext.add(getFloatArrayListFromReadableArray(deltaX)); mDeltaYContext.add(getFloatArrayListFromReadableArray(deltaY)); mXContext.add(location.x); + mYContext.add(location.y); + mCurrentDelta = clonePointF(mCurrentDelta); mCurrentLocation = clonePointF(location); mContextLength++; } public void popContext() { float x = mXContext.get(mContextLength - 1); + float y = mYContext.get(mContextLength - 1); mFontContext.remove(mContextLength - 1); mLocationContext.remove(mContextLength - 1); mDeltaContext.remove(mContextLength - 1); mDeltaXContext.remove(mContextLength - 1); mDeltaYContext.remove(mContextLength - 1); mXContext.remove(mContextLength - 1); + mYContext.remove(mContextLength - 1); mContextLength--; if (mContextLength != 0) { mXContext.set(mContextLength - 1, x); + mYContext.set(mContextLength - 1, y); PointF lastLocation = mLocationContext.get(mContextLength - 1); + PointF lastDelta = mDeltaContext.get(mContextLength - 1); mCurrentLocation = clonePointF(lastLocation); + mCurrentDelta = clonePointF(lastDelta); mCurrentLocation.x = lastLocation.x = x; + mCurrentLocation.y = lastLocation.y = y; } } public PointF getNextGlyphPoint(float offset, float glyphWidth) { mXContext.set(mXContext.size() - 1, mCurrentLocation.x + offset + glyphWidth); + mYContext.set(mYContext.size() - 1, mCurrentLocation.y); return new PointF(mCurrentLocation.x + offset, mCurrentLocation.y); - } public PointF getNextGlyphDelta() { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index f0a633a44..76e1b59ee 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -114,8 +114,7 @@ private Path getLinePath(String line, Paint paint, Path path) { continue; } - matrix.preTranslate(0, glyphDelta.y); - matrix.postTranslate(0, glyphPoint.y); + matrix.postTranslate(0, glyphPoint.y + glyphDelta.y); } else { matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); } From fe1b9b2ea8d72b570ef4c91dcabf98d8c0d61455 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 19 Jun 2017 18:47:38 +0300 Subject: [PATCH 015/198] Improve spec conformance of text. Clear dx if x or dy if y is set in a GlyphContext. Make glyph preTranslate by dy instead of postTranslate. Scale glyph delta correctly. --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 12 ++++++++---- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 8750ec479..88c8e16f1 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -56,23 +56,27 @@ public class GlyphContext { public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { PointF location = mCurrentLocation; + mDeltaContext.add(mCurrentDelta); + if (positionX != null) { location.x = PropHelper.fromPercentageToFloat(positionX, mWidth, 0, mScale); + mCurrentDelta.x = 0; } if (positionY != null) { location.y = PropHelper.fromPercentageToFloat(positionY, mHeight, 0, mScale); + mCurrentDelta.y = 0; } + mCurrentDelta = clonePointF(mCurrentDelta); + mLocationContext.add(location); - mDeltaContext.add(mCurrentDelta); mFontContext.add(font); mDeltaXContext.add(getFloatArrayListFromReadableArray(deltaX)); mDeltaYContext.add(getFloatArrayListFromReadableArray(deltaY)); mXContext.add(location.x); mYContext.add(location.y); - mCurrentDelta = clonePointF(mCurrentDelta); mCurrentLocation = clonePointF(location); mContextLength++; } @@ -110,8 +114,8 @@ public PointF getNextGlyphPoint(float offset, float glyphWidth) { } public PointF getNextGlyphDelta() { - float dx = getNextDelta(mDeltaXContext); - float dy = getNextDelta(mDeltaYContext); + float dx = mScale * getNextDelta(mDeltaXContext); + float dy = mScale * getNextDelta(mDeltaYContext); if (mContextLength > 0) { for (PointF point: mDeltaContext) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 76e1b59ee..f0a633a44 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -114,7 +114,8 @@ private Path getLinePath(String line, Paint paint, Path path) { continue; } - matrix.postTranslate(0, glyphPoint.y + glyphDelta.y); + matrix.preTranslate(0, glyphDelta.y); + matrix.postTranslate(0, glyphPoint.y); } else { matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); } From b094bc80f050ba97c1892b9984cdcda9c270e4fc Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 19 Jun 2017 22:04:12 +0300 Subject: [PATCH 016/198] Seemingly pixel-perfect rendering of toap examples from svg spec. Set paint flags for anti-alias, kerning for text, subpixel positioning of text. Add half of width of glyph to distance calculation for mBezierTransformer.getTransformAtDistance and preTranslate the glyph corresponding amount backwards (gives correct angles on glyphs when rendering text on a path) Add magical constant ratios of 1.2 into getBezierTransformer and getGlyphPointFromContext calls (should probably be based on some dpi ratio calculation) --- .../src/main/java/com/horcrux/svg/SvgViewShadowNode.java | 5 +++++ android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 6 +++--- .../src/main/java/com/horcrux/svg/TextPathShadowNode.java | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index 1d60f88c5..291e7c401 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -16,6 +16,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Typeface; import android.util.Base64; import com.facebook.react.uimanager.DisplayMetricsHolder; @@ -135,6 +136,10 @@ private void drawChildren(Canvas canvas) { Paint paint = new Paint(); + paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG); + + paint.setTypeface(Typeface.DEFAULT); + for (int i = 0; i < getChildCount(); i++) { if (!(getChildAt(i) instanceof VirtualNode)) { continue; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index f0a633a44..c5cd94dd6 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -101,12 +101,12 @@ private Path getLinePath(String line, Paint paint, Path path) { paint.getTextPath(letter, 0, 1, 0, 0, glyph); PointF glyphDelta = getGlyphDeltaFromContext(); - PointF glyphPoint = getGlyphPointFromContext(glyphPosition, width); + PointF glyphPoint = getGlyphPointFromContext(glyphPosition * 1.2f, width); glyphPosition += width; Matrix matrix = new Matrix(); if (mBezierTransformer != null) { - matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x + glyphDelta.x); + matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x + glyphDelta.x + width / 2); if (textPathHasReachedEnd()) { break; @@ -114,7 +114,7 @@ private Path getLinePath(String line, Paint paint, Path path) { continue; } - matrix.preTranslate(0, glyphDelta.y); + matrix.preTranslate(-width / 2, glyphDelta.y); matrix.postTranslate(0, glyphPoint.y); } else { matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index ec48c17c5..f3f88c54e 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -52,7 +52,7 @@ public BezierTransformer getBezierTransformer() { } PathShadowNode path = (PathShadowNode)template; - return new BezierTransformer(path.getBezierCurves(), relativeOnWidth(mStartOffset)); + return new BezierTransformer(path.getBezierCurves(), relativeOnWidth(mStartOffset) / 1.2f); } @Override From 155fa4b6aed74b508f54278c9acbd67dfd260cec Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 20 Jun 2017 03:15:01 +0300 Subject: [PATCH 017/198] Implement support for em in dx and dy attributes and font props for G element --- .../java/com/horcrux/svg/GlyphContext.java | 31 ++++++++- .../java/com/horcrux/svg/GroupShadowNode.java | 67 +++++++++++++++++++ .../java/com/horcrux/svg/TextShadowNode.java | 58 +--------------- elements/G.js | 9 ++- lib/attributes.js | 7 +- lib/extract/extractText.js | 9 ++- 6 files changed, 117 insertions(+), 64 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 88c8e16f1..5a9567aed 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -53,6 +53,24 @@ public class GlyphContext { mYContext = new ArrayList<>(); } + public void pushContext(@Nullable ReadableMap font) { + PointF location = mCurrentLocation; + + mDeltaContext.add(mCurrentDelta); + + mCurrentDelta = clonePointF(mCurrentDelta); + + mLocationContext.add(location); + mFontContext.add(font); + mDeltaXContext.add(new ArrayList()); + mDeltaYContext.add(new ArrayList()); + mXContext.add(location.x); + mYContext.add(location.y); + + mCurrentLocation = clonePointF(location); + mContextLength++; + } + public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { PointF location = mCurrentLocation; @@ -194,10 +212,21 @@ public ReadableMap getGlyphFont() { private ArrayList getFloatArrayListFromReadableArray(ReadableArray readableArray) { ArrayList arrayList = new ArrayList<>(); + ReadableMap font = getGlyphFont(); + float fontSize = (float)font.getDouble("fontSize"); if (readableArray != null) { for (int i = 0; i < readableArray.size(); i++) { - arrayList.add((float)readableArray.getDouble(i)); + switch (readableArray.getType(i)) { + case String: + String val = readableArray.getString(i); + arrayList.add(Float.valueOf(val.substring(0, val.length() - 2)) * fontSize); + break; + + case Number: + arrayList.add((float)readableArray.getDouble(i)); + break; + } } } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index c4ece1973..b0d176ab1 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -14,8 +14,11 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; +import android.graphics.PointF; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; +import com.facebook.react.uimanager.annotations.ReactProp; import javax.annotation.Nullable; @@ -24,8 +27,70 @@ * Shadow node for virtual Group view */ public class GroupShadowNode extends RenderableShadowNode { + protected @Nullable ReadableMap mFont; + + private GlyphContext mGlyphContext; + private GroupShadowNode mTextRoot; + + @ReactProp(name = "font") + public void setFont(@Nullable ReadableMap font) { + mFont = font; + markUpdated(); + } + + protected GroupShadowNode getTextRoot() { + if (mTextRoot == null) { + mTextRoot = this; + + while (mTextRoot != null) { + if (mTextRoot.getClass() == GroupShadowNode.class) { + break; + } + + ReactShadowNode parent = mTextRoot.getParent(); + + if (!(parent instanceof GroupShadowNode)) { + //todo: throw exception here + mTextRoot = null; + } else { + mTextRoot = (GroupShadowNode)parent; + } + } + } + + return mTextRoot; + } + + protected void setupGlyphContext() { + mGlyphContext = new GlyphContext(mScale, getCanvasWidth(), getCanvasHeight()); + } + + protected GlyphContext getGlyphContext() { + return mGlyphContext; + } + + protected void pushGlyphContext() { + getTextRoot().getGlyphContext().pushContext(mFont); + } + + protected void popGlyphContext() { + getTextRoot().getGlyphContext().popContext(); + } + + protected ReadableMap getFontFromContext() { + return getTextRoot().getGlyphContext().getGlyphFont(); + } + + protected PointF getGlyphPointFromContext(float offset, float glyphWidth) { + return getTextRoot().getGlyphContext().getNextGlyphPoint(offset, glyphWidth); + } + + protected PointF getGlyphDeltaFromContext() { + return getTextRoot().getGlyphContext().getNextGlyphDelta(); + } public void draw(final Canvas canvas, final Paint paint, final float opacity) { + setupGlyphContext(); if (opacity > MIN_OPACITY_FOR_DRAW) { clip(canvas, paint); drawGroup(canvas, paint, opacity); @@ -33,6 +98,7 @@ public void draw(final Canvas canvas, final Paint paint, final float opacity) { } protected void drawGroup(final Canvas canvas, final Paint paint, final float opacity) { + pushGlyphContext(); final SvgViewShadowNode svg = getSvgShadowNode(); final GroupShadowNode self = this; traverseChildren(new NodeRunnable() { @@ -57,6 +123,7 @@ public boolean run(VirtualNode node) { return true; } }); + popGlyphContext(); } protected void drawPath(Canvas canvas, Paint paint, float opacity) { diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index a38c855ab..651318f31 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -43,10 +43,6 @@ public class TextShadowNode extends GroupShadowNode { private @Nullable ReadableArray mDeltaY; private @Nullable String mPositionX; private @Nullable String mPositionY; - private @Nullable ReadableMap mFont; - - private GlyphContext mGlyphContext; - private TextShadowNode mTextRoot; @ReactProp(name = "textAnchor", defaultInt = TEXT_ANCHOR_AUTO) public void setTextAnchor(int textAnchor) { @@ -108,12 +104,6 @@ protected Path getPath(Canvas canvas, Paint paint) { return groupPath; } - protected void drawGroup(Canvas canvas, Paint paint, float opacity) { - pushGlyphContext(); - super.drawGroup(canvas, paint, opacity); - popGlyphContext(); - } - private int getTextAnchor() { return mTextAnchor; } @@ -135,33 +125,6 @@ private int getComputedTextAnchor() { return anchor; } - private TextShadowNode getTextRoot() { - if (mTextRoot == null) { - mTextRoot = this; - - while (mTextRoot != null) { - if (mTextRoot.getClass() == TextShadowNode.class) { - break; - } - - ReactShadowNode parent = mTextRoot.getParent(); - - if (!(parent instanceof TextShadowNode)) { - //todo: throw exception here - mTextRoot = null; - } else { - mTextRoot = (TextShadowNode)parent; - } - } - } - - return mTextRoot; - } - - private void setupGlyphContext() { - mGlyphContext = new GlyphContext(mScale, getCanvasWidth(), getCanvasHeight()); - } - protected void releaseCachedPath() { traverseChildren(new NodeRunnable() { public boolean run(VirtualNode node) { @@ -180,30 +143,11 @@ protected Path getGroupPath(Canvas canvas, Paint paint) { return groupPath; } - protected GlyphContext getGlyphContext() { - return mGlyphContext; - } - + @Override protected void pushGlyphContext() { getTextRoot().getGlyphContext().pushContext(mFont, mDeltaX, mDeltaY, mPositionX, mPositionY); } - protected void popGlyphContext() { - getTextRoot().getGlyphContext().popContext(); - } - - protected ReadableMap getFontFromContext() { - return getTextRoot().getGlyphContext().getGlyphFont(); - } - - protected PointF getGlyphPointFromContext(float offset, float glyphWidth) { - return getTextRoot().getGlyphContext().getNextGlyphPoint(offset, glyphWidth); - } - - protected PointF getGlyphDeltaFromContext() { - return getTextRoot().getGlyphContext().getNextGlyphDelta(); - } - private Matrix getAlignMatrix(Path path) { RectF box = new RectF(); path.computeBounds(box, true); diff --git a/elements/G.js b/elements/G.js index 300309d36..92c1a66eb 100644 --- a/elements/G.js +++ b/elements/G.js @@ -1,14 +1,18 @@ import React from 'react'; import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import Shape from './Shape'; -import {pathProps} from '../lib/props'; +import {pathProps, fontProps} from '../lib/props'; import {GroupAttributes} from '../lib/attributes'; import extractProps from '../lib/extract/extractProps'; +import {extractFont} from '../lib/extract/extractText'; export default class extends Shape{ static displayName = 'G'; - static propTypes = pathProps; + static propTypes = { + ...pathProps, + ...fontProps, + }; setNativeProps = (...args) => { this.root.setNativeProps(...args); @@ -19,6 +23,7 @@ export default class extends Shape{ return {this.root = ele;}} > {props.children} diff --git a/lib/attributes.js b/lib/attributes.js index 575be4fed..c7ad65d96 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -74,7 +74,12 @@ const RenderableAttributes = { ...FillAndStrokeAttributes }; -const GroupAttributes = RenderableAttributes; +const GroupAttributes = { + font: { + diff: fontDiffer + }, + ...RenderableAttributes +}; const UseAttributes = { href: true, diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index b7e628d97..35fabcfdd 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -47,7 +47,7 @@ function parseFontString(font) { return cachedFontObjectsFromString[font]; } -function extractFont(props) { +export function extractFont(props) { let font = props.font; let fontSize = +props.fontSize; @@ -68,8 +68,11 @@ function extractFont(props) { function parseDelta(delta) { if (typeof delta === 'string') { - if (isNaN(+delta)) { - return delta.trim().replace(commaReg, ' ').split(spaceReg).map(d => +d || 0); + const trim = delta.trim(); + if (trim.slice(-2) === 'em') { + return [trim]; + } else if (isNaN(+delta)) { + return trim.replace(commaReg, ' ').split(spaceReg).map(d => +d || 0); } else { return [+delta]; } From a168a2ae6da98d83700d31aabc60791e67ce7d9a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 20 Jun 2017 03:40:47 +0300 Subject: [PATCH 018/198] Ensure that getTextRoot and getGlyphContext works correctly independent of if it is wrapped by a G element or not --- .../main/java/com/horcrux/svg/GroupShadowNode.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index b0d176ab1..ea48a6e54 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -39,11 +39,20 @@ public void setFont(@Nullable ReadableMap font) { } protected GroupShadowNode getTextRoot() { + GroupShadowNode shadowNode = getShadowNode(GroupShadowNode.class); + if (shadowNode == null) { + return getShadowNode(TextShadowNode.class); + } + return shadowNode; + } + + @android.support.annotation.Nullable + private GroupShadowNode getShadowNode(Class shadowNodeClass) { if (mTextRoot == null) { mTextRoot = this; while (mTextRoot != null) { - if (mTextRoot.getClass() == GroupShadowNode.class) { + if (mTextRoot.getClass() == shadowNodeClass) { break; } @@ -66,6 +75,9 @@ protected void setupGlyphContext() { } protected GlyphContext getGlyphContext() { + if (mGlyphContext == null) { + setupGlyphContext(); + } return mGlyphContext; } From dac969b9446c8d3d92b3c7dff185906ce11e554a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 20 Jun 2017 22:04:41 +0300 Subject: [PATCH 019/198] First letterSpacing implementation attempt --- .../java/com/horcrux/svg/GlyphContext.java | 22 ++++++++++++++++++- .../java/com/horcrux/svg/TSpanShadowNode.java | 22 +++++++++++++++---- lib/attributes.js | 4 +++- lib/extract/extractText.js | 4 +++- lib/props.js | 2 ++ 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 5a9567aed..e87daa34b 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -37,6 +37,8 @@ public class GlyphContext { private float mHeight; private int mContextLength = 0; private static final float DEFAULT_FONT_SIZE = 12f; + private static final float DEFAULT_KERNING = 0f; + private static final float DEFAULT_LETTER_SPACING = 0f; GlyphContext(float scale, float width, float height) { mScale = scale; @@ -171,7 +173,11 @@ private float getNextDelta(ArrayList> deltaContext) { public ReadableMap getGlyphFont() { String fontFamily = null; float fontSize = DEFAULT_FONT_SIZE; + float kerning = DEFAULT_KERNING; + float letterSpacing = DEFAULT_LETTER_SPACING; boolean fontSizeSet = false; + boolean kerningSet = false; + boolean letterSpacingSet = false; String fontWeight = null; String fontStyle = null; @@ -189,6 +195,18 @@ public ReadableMap getGlyphFont() { fontSizeSet = true; } + // TODO: add support for other length units + if (!kerningSet && font.hasKey("kerning")) { + kerning = Float.valueOf(font.getString("kerning")); + kerningSet = true; + } + + // TODO: add support for other length units + if (!letterSpacingSet && font.hasKey("letterSpacing")) { + letterSpacing = Float.valueOf(font.getString("letterSpacing")); + letterSpacingSet = true; + } + if (fontWeight == null && font.hasKey("fontWeight")) { fontWeight = font.getString("fontWeight"); } @@ -196,7 +214,7 @@ public ReadableMap getGlyphFont() { fontStyle = font.getString("fontStyle"); } - if (fontFamily != null && fontSizeSet && fontWeight != null && fontStyle != null) { + if (fontFamily != null && fontSizeSet && kerningSet && letterSpacingSet && fontWeight != null && fontStyle != null) { break; } } @@ -206,6 +224,8 @@ public ReadableMap getGlyphFont() { map.putDouble("fontSize", fontSize); map.putString("fontWeight", fontWeight); map.putString("fontStyle", fontStyle); + map.putDouble("kerning", kerning); + map.putDouble("letterSpacing", letterSpacing); return map; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index c5cd94dd6..612c390f6 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -39,6 +39,8 @@ public class TSpanShadowNode extends TextShadowNode { 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 String PROP_KERNING = "kerning"; + private static final String PROP_LETTER_SPACING = "letterSpacing"; @ReactProp(name = "content") public void setContent(@Nullable String content) { @@ -101,12 +103,12 @@ private Path getLinePath(String line, Paint paint, Path path) { paint.getTextPath(letter, 0, 1, 0, 0, glyph); PointF glyphDelta = getGlyphDeltaFromContext(); - PointF glyphPoint = getGlyphPointFromContext(glyphPosition * 1.2f, width); + PointF glyphPoint = getGlyphPointFromContext(glyphPosition, width); glyphPosition += width; Matrix matrix = new Matrix(); if (mBezierTransformer != null) { - matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x + glyphDelta.x + width / 2); + matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x * 1.2f + glyphDelta.x + width / 2); if (textPathHasReachedEnd()) { break; @@ -132,10 +134,22 @@ private void applyTextPropertiesToPaint(Paint paint) { paint.setTextAlign(Paint.Align.LEFT); - float fontSize = (float)font.getDouble(PROP_FONT_SIZE); + float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale; + float kerning = (float)font.getDouble(PROP_KERNING); + float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING); - paint.setTextSize(fontSize * mScale); + if (mBezierTransformer == null) { + letterSpacing *= mScale; + } else { + // What is going on here? This helps get closer to how e.g. chrome renders things + // But, still off, depending on the font size and letter-spacing. + letterSpacing *= java.lang.Math.pow(120 / fontSize, 6); + } + paint.setTextSize(fontSize); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + paint.setLetterSpacing(letterSpacing / fontSize); // setLetterSpacing is only available from LOLLIPOP and on + } 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)); diff --git a/lib/attributes.js b/lib/attributes.js index c7ad65d96..83c7c31ca 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -23,7 +23,9 @@ function fontDiffer(a, b) { return a.fontSize !== b.fontSize || a.fontFamily !== b.fontFamily || a.fontStyle !== b.fontStyle || - a.fontWeight !== b.fontWeight; + a.fontWeight !== b.fontWeight || + a.kerning !== b.kerning || + a.letterSpacing !== b.letterSpacing; } const ViewBoxAttributes = { diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 35fabcfdd..17fca9d79 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -55,7 +55,9 @@ export function extractFont(props) { fontFamily: extractSingleFontFamily(props.fontFamily), fontSize: isNaN(fontSize) ? null : fontSize, fontWeight: props.fontWeight, - fontStyle: props.fontStyle + fontStyle: props.fontStyle, + kerning: props.kerning, + letterSpacing: props.letterSpacing, }; if (typeof props.font === 'string') { diff --git a/lib/props.js b/lib/props.js index e074a468e..9ad226ab3 100644 --- a/lib/props.js +++ b/lib/props.js @@ -53,6 +53,8 @@ const fontProps = { fontSize: numberProp, fontWeight: numberProp, fontStyle: PropTypes.string, + letterSpacing: PropTypes.string, + kerning: PropTypes.string, font: PropTypes.object }; From 8c3d2552d2a0283eaa4d147bd78c2866c33a5f5f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Jun 2017 17:21:03 +0300 Subject: [PATCH 020/198] Implement default / auto kerning and debug helpers. Remove magic constants. Implement canvas.drawTextOnPath for reference comparison. Refactor PathShadowNode and BezierTransformer, expose getPath and getmStartOffset, implement inefficient but accurate enough getTotalDistance for debugging/diagnostics purposes. --- .../com/horcrux/svg/BezierTransformer.java | 53 +++++++++++++++-- .../java/com/horcrux/svg/PathShadowNode.java | 4 ++ .../com/horcrux/svg/RenderableShadowNode.java | 6 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 59 +++++++++++++------ .../com/horcrux/svg/TextPathShadowNode.java | 2 +- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/BezierTransformer.java b/android/src/main/java/com/horcrux/svg/BezierTransformer.java index 9c27084ea..5ae1b542e 100644 --- a/android/src/main/java/com/horcrux/svg/BezierTransformer.java +++ b/android/src/main/java/com/horcrux/svg/BezierTransformer.java @@ -17,6 +17,7 @@ public class BezierTransformer { private ReadableArray mBezierCurves; + private PathShadowNode mPath; private int mCurrentBezierIndex = 0; private float mStartOffset = 0f; private float mLastOffset = 0f; @@ -30,9 +31,10 @@ public class BezierTransformer { private boolean mReachedStart; private boolean mReachedEnd; - BezierTransformer(ReadableArray bezierCurves, float startOffset) { - mBezierCurves = bezierCurves; + BezierTransformer(PathShadowNode path, float startOffset) { + mBezierCurves = path.getBezierCurves(); mStartOffset = startOffset; + mPath = path; } private float calculateBezier(float t, float P0, float P1, float P2, float P3) { @@ -100,8 +102,51 @@ private void setControlPoints() { } } + public float getmStartOffset() { + return mStartOffset; + } + + public PathShadowNode getPath() { + return mPath; + } + + public float getTotalDistance() { + float distance = 0; + + while (!mReachedEnd) { + distance += 0.1f; + float offset = offsetAtDistance(distance - mLastRecord, mLastPoint, mLastOffset); + + if (offset < 1) { + PointF glyphPoint = pointAtOffset(offset); + mLastOffset = offset; + mLastPoint = glyphPoint; + mLastRecord = distance; + } else if (mBezierCurves.size() == mCurrentBezierIndex) { + mReachedEnd = true; + } else { + mLastOffset = 0; + mLastPoint = mP0 = mP3; + mLastRecord += mLastDistance; + setControlPoints(); + } + } + + mCurrentBezierIndex = 0; + mLastOffset = 0f; + mLastRecord = 0f; + mLastDistance = 0f; + mLastPoint = new PointF(); + mP0 = new PointF(); + mP1 = new PointF(); + mP2 = new PointF(); + mP3 = new PointF(); + mReachedEnd = false; + + return distance; + } + public Matrix getTransformAtDistance(float distance) { - distance += mStartOffset; mReachedStart = distance >= 0; if (mReachedEnd || !mReachedStart) { @@ -127,7 +172,7 @@ public Matrix getTransformAtDistance(float distance) { mLastPoint = mP0 = mP3; mLastRecord += mLastDistance; setControlPoints(); - return getTransformAtDistance(distance - mStartOffset); + return getTransformAtDistance(distance); } } diff --git a/android/src/main/java/com/horcrux/svg/PathShadowNode.java b/android/src/main/java/com/horcrux/svg/PathShadowNode.java index f9fa6739e..15d4d0e33 100644 --- a/android/src/main/java/com/horcrux/svg/PathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/PathShadowNode.java @@ -36,6 +36,10 @@ protected Path getPath(Canvas canvas, Paint paint) { return mPath; } + public Path getPath() { + return mPath; + } + public ReadableArray getBezierCurves() { return mD.getBezierCurves(); } diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 2e015be01..90f16b7ca 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -221,7 +221,7 @@ public void draw(Canvas canvas, Paint paint, float opacity) { protected boolean setupFillPaint(Paint paint, float opacity) { if (mFill != null && mFill.size() > 0) { paint.reset(); - paint.setFlags(Paint.ANTI_ALIAS_FLAG); + paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG); paint.setStyle(Paint.Style.FILL); setupPaint(paint, opacity, mFill); return true; @@ -239,7 +239,7 @@ protected boolean setupStrokePaint(Paint paint, float opacity) { return false; } - paint.setFlags(Paint.ANTI_ALIAS_FLAG); + paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG); paint.setStyle(Paint.Style.STROKE); paint.setStrokeCap(mStrokeLinecap); paint.setStrokeJoin(mStrokeLinejoin); @@ -327,7 +327,7 @@ public void mergeProperties(RenderableShadowNode target) { targetAttributeList.size() == 0) { return; } - + mOriginProperties = new ArrayList<>(); mAttributeList = clonePropList(); diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 612c390f6..e9a2ae22b 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -34,6 +34,7 @@ public class TSpanShadowNode extends TextShadowNode { private BezierTransformer mBezierTransformer; private Path mCache; private @Nullable String mContent; + private boolean debug = false; private static final String PROP_FONT_FAMILY = "fontFamily"; private static final String PROP_FONT_SIZE = "fontSize"; @@ -80,7 +81,7 @@ protected Path getPath(Canvas canvas, Paint paint) { pushGlyphContext(); applyTextPropertiesToPaint(paint); - getLinePath(mContent + "", paint, path); + getLinePath(canvas, mContent + "", paint, path); mCache = path; popGlyphContext(); @@ -91,24 +92,50 @@ protected Path getPath(Canvas canvas, Paint paint) { return path; } - private Path getLinePath(String line, Paint paint, Path path) { - float[] widths = new float[line.length()]; + private Path getLinePath(Canvas canvas, String line, Paint paint, Path path) { + PointF glyphPoint = getGlyphPointFromContext(0, 0); + PointF glyphDelta = getGlyphDeltaFromContext(); + PathShadowNode p = null; + Path bezierPath = null; + Path glyph = null; + float width = 0; + float offset = 0; + float distance = 0; + float textMeasure = 0; + + int chars = line.length(); + final float[] widths = new float[chars]; paint.getTextWidths(line, widths); float glyphPosition = 0f; + if (mBezierTransformer != null) { + distance = mBezierTransformer.getTotalDistance(); + p = mBezierTransformer.getPath(); + offset = mBezierTransformer.getmStartOffset(); + bezierPath = p.getPath(); + textMeasure = paint.measureText(line); + if (debug) { + canvas.drawTextOnPath(line, bezierPath, offset + glyphPoint.x + glyphDelta.x, glyphDelta.y, paint); + } + } + for (int index = 0; index < line.length(); index++) { String letter = line.substring(index, index + 1); - Path glyph = new Path(); - float width = widths[index]; + glyph = new Path(); + width = widths[index]; + float uptoChar = paint.measureText(line, Math.max(0, index - 1), index); + float untilChar = paint.measureText(line, Math.max(0, index - 1), index + 1); + float onlyChar = paint.measureText(line, index, index + 1); + float kerned = untilChar - uptoChar; + float kerning = kerned - onlyChar; + glyphPosition += kerning; paint.getTextPath(letter, 0, 1, 0, 0, glyph); - PointF glyphDelta = getGlyphDeltaFromContext(); - PointF glyphPoint = getGlyphPointFromContext(glyphPosition, width); - glyphPosition += width; + glyphPoint = getGlyphPointFromContext(glyphPosition, width); Matrix matrix = new Matrix(); if (mBezierTransformer != null) { - matrix = mBezierTransformer.getTransformAtDistance(glyphPoint.x * 1.2f + glyphDelta.x + width / 2); + matrix = mBezierTransformer.getTransformAtDistance(offset + glyphPoint.x + glyphDelta.x + width / 2); if (textPathHasReachedEnd()) { break; @@ -122,8 +149,10 @@ private Path getLinePath(String line, Paint paint, Path path) { matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); } + glyphPosition += width; glyph.transform(matrix); path.addPath(glyph); + glyphDelta = getGlyphDeltaFromContext(); } return path; @@ -135,16 +164,8 @@ private void applyTextPropertiesToPaint(Paint paint) { paint.setTextAlign(Paint.Align.LEFT); float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale; - float kerning = (float)font.getDouble(PROP_KERNING); - float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING); - - if (mBezierTransformer == null) { - letterSpacing *= mScale; - } else { - // What is going on here? This helps get closer to how e.g. chrome renders things - // But, still off, depending on the font size and letter-spacing. - letterSpacing *= java.lang.Math.pow(120 / fontSize, 6); - } + float kerning = (float)font.getDouble(PROP_KERNING) * mScale; + float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale; paint.setTextSize(fontSize); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index f3f88c54e..fb6221c66 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -52,7 +52,7 @@ public BezierTransformer getBezierTransformer() { } PathShadowNode path = (PathShadowNode)template; - return new BezierTransformer(path.getBezierCurves(), relativeOnWidth(mStartOffset) / 1.2f); + return new BezierTransformer(path, relativeOnWidth(mStartOffset)); } @Override From 4fea95398e392e644b077adaacc07254f731dd9b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Jun 2017 19:11:41 +0300 Subject: [PATCH 021/198] Implement support for kerning with length value. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘kerning’ indicates whether the user agent should adjust inter-glyph spacing based on kerning tables that are included in the relevant font (i.e., enable auto-kerning) or instead disable auto-kerning and instead set inter-character spacing to a specific length (typically, zero). https://www.w3.org/TR/SVG/text.html#SpacingProperties https://www.w3.org/TR/SVG/text.html#KerningProperty --- .../java/com/horcrux/svg/GlyphContext.java | 1 + .../java/com/horcrux/svg/TSpanShadowNode.java | 37 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index e87daa34b..1fe3468f4 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -226,6 +226,7 @@ public ReadableMap getGlyphFont() { map.putString("fontStyle", fontStyle); map.putDouble("kerning", kerning); map.putDouble("letterSpacing", letterSpacing); + map.putBoolean("isKerningValueSet", kerningSet); return map; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index e9a2ae22b..a91421c22 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -41,6 +41,7 @@ public class TSpanShadowNode extends TextShadowNode { private static final String PROP_FONT_STYLE = "fontStyle"; private static final String PROP_FONT_WEIGHT = "fontWeight"; private static final String PROP_KERNING = "kerning"; + private static final String PROP_IS_KERNING_VALUE_SET = "isKerningValueSet"; private static final String PROP_LETTER_SPACING = "letterSpacing"; @ReactProp(name = "content") @@ -80,7 +81,6 @@ protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); pushGlyphContext(); - applyTextPropertiesToPaint(paint); getLinePath(canvas, mContent + "", paint, path); mCache = path; @@ -93,20 +93,27 @@ protected Path getPath(Canvas canvas, Paint paint) { } private Path getLinePath(Canvas canvas, String line, Paint paint, Path path) { + ReadableMap font = applyTextPropertiesToPaint(paint); + PointF glyphPoint = getGlyphPointFromContext(0, 0); PointF glyphDelta = getGlyphDeltaFromContext(); + PathShadowNode p = null; Path bezierPath = null; Path glyph = null; + float width = 0; float offset = 0; float distance = 0; float textMeasure = 0; + float glyphPosition = 0; int chars = line.length(); final float[] widths = new float[chars]; paint.getTextWidths(line, widths); - float glyphPosition = 0f; + + float kerningValue = (float)font.getDouble(PROP_KERNING) * mScale; + boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); if (mBezierTransformer != null) { distance = mBezierTransformer.getTotalDistance(); @@ -123,12 +130,17 @@ private Path getLinePath(Canvas canvas, String line, Paint paint, Path path) { String letter = line.substring(index, index + 1); glyph = new Path(); width = widths[index]; - float uptoChar = paint.measureText(line, Math.max(0, index - 1), index); - float untilChar = paint.measureText(line, Math.max(0, index - 1), index + 1); - float onlyChar = paint.measureText(line, index, index + 1); - float kerned = untilChar - uptoChar; - float kerning = kerned - onlyChar; - glyphPosition += kerning; + + if (isKerningValueSet) { + glyphPosition += kerningValue; + } else { + float previousChar = paint.measureText(line, Math.max(0, index - 1), index); + float previousAndCurrentChar = paint.measureText(line, Math.max(0, index - 1), index + 1); + float onlyCurrentChar = paint.measureText(line, index, index + 1); + float kernedCharWidth = previousAndCurrentChar - previousChar; + float kerning = kernedCharWidth - onlyCurrentChar; + glyphPosition += kerning; + } paint.getTextPath(letter, 0, 1, 0, 0, glyph); glyphPoint = getGlyphPointFromContext(glyphPosition, width); @@ -149,22 +161,21 @@ private Path getLinePath(Canvas canvas, String line, Paint paint, Path path) { matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); } - glyphPosition += width; + glyphDelta = getGlyphDeltaFromContext(); glyph.transform(matrix); + glyphPosition += width; path.addPath(glyph); - glyphDelta = getGlyphDeltaFromContext(); } return path; } - private void applyTextPropertiesToPaint(Paint paint) { + private ReadableMap applyTextPropertiesToPaint(Paint paint) { ReadableMap font = getFontFromContext(); paint.setTextAlign(Paint.Align.LEFT); float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale; - float kerning = (float)font.getDouble(PROP_KERNING) * mScale; float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale; paint.setTextSize(fontSize); @@ -187,6 +198,8 @@ private void applyTextPropertiesToPaint(Paint paint) { } // NB: if the font family is null / unsupported, the default one will be used paint.setTypeface(Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle)); + + return font; } private void setupTextPath() { From 9d9dbd598ea4b7c3934cda0c3b8e5d645333738e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Jun 2017 21:42:34 +0300 Subject: [PATCH 022/198] Optimize, refactor, cleanup. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 97 +++++++++++-------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index a91421c22..64131c7b8 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -71,83 +71,94 @@ protected Path getPath(Canvas canvas, Paint paint) { return mCache; } - String text = mContent; - - if (text == null) { + if (mContent == null) { return getGroupPath(canvas, paint); } setupTextPath(); - Path path = new Path(); pushGlyphContext(); - getLinePath(canvas, mContent + "", paint, path); - - mCache = path; + Path path = mCache = getLinePath(canvas, mContent, paint); popGlyphContext(); - RectF box = new RectF(); - path.computeBounds(box, true); + path.computeBounds(new RectF(), true); return path; } - private Path getLinePath(Canvas canvas, String line, Paint paint, Path path) { - ReadableMap font = applyTextPropertiesToPaint(paint); + private Path getLinePath(Canvas canvas, String line, Paint paint) { + int length = line.length(); + Path path = new Path(); + + if (length == 0) { + return path; + } PointF glyphPoint = getGlyphPointFromContext(0, 0); PointF glyphDelta = getGlyphDeltaFromContext(); - - PathShadowNode p = null; - Path bezierPath = null; - Path glyph = null; - - float width = 0; - float offset = 0; - float distance = 0; float textMeasure = 0; - float glyphPosition = 0; - - int chars = line.length(); - final float[] widths = new float[chars]; - paint.getTextWidths(line, widths); - - float kerningValue = (float)font.getDouble(PROP_KERNING) * mScale; - boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); + float distance = 0; + float offset = 0; + PathShadowNode p; + Path bezierPath; if (mBezierTransformer != null) { distance = mBezierTransformer.getTotalDistance(); - p = mBezierTransformer.getPath(); offset = mBezierTransformer.getmStartOffset(); - bezierPath = p.getPath(); textMeasure = paint.measureText(line); + p = mBezierTransformer.getPath(); + bezierPath = p.getPath(); if (debug) { - canvas.drawTextOnPath(line, bezierPath, offset + glyphPoint.x + glyphDelta.x, glyphDelta.y, paint); + canvas.drawTextOnPath( + line, + bezierPath, + offset + glyphPoint.x + glyphDelta.x, + glyphDelta.y, + paint + ); } } - for (int index = 0; index < line.length(); index++) { - String letter = line.substring(index, index + 1); - glyph = new Path(); + Path glyph; + float width; + Matrix matrix; + String current; + String previous = ""; + float glyphPosition = 0; + char[] chars = line.toCharArray(); + float[] widths = new float[length]; + + ReadableMap font = applyTextPropertiesToPaint(paint); + double kerningValue = font.getDouble(PROP_KERNING) * mScale; + boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); + + paint.getTextWidths(line, widths); + + for (int index = 0; index < length; index++) { + current = String.valueOf(chars[index]); width = widths[index]; + glyph = new Path(); if (isKerningValueSet) { glyphPosition += kerningValue; } else { - float previousChar = paint.measureText(line, Math.max(0, index - 1), index); - float previousAndCurrentChar = paint.measureText(line, Math.max(0, index - 1), index + 1); - float onlyCurrentChar = paint.measureText(line, index, index + 1); - float kernedCharWidth = previousAndCurrentChar - previousChar; - float kerning = kernedCharWidth - onlyCurrentChar; + float bothWidth = paint.measureText(previous + current); + float previousWidth = paint.measureText(previous); + float currentWidth = paint.measureText(current); + float kernedWidth = bothWidth - previousWidth; + float kerning = kernedWidth - currentWidth; glyphPosition += kerning; + previous = current; } - paint.getTextPath(letter, 0, 1, 0, 0, glyph); glyphPoint = getGlyphPointFromContext(glyphPosition, width); - Matrix matrix = new Matrix(); if (mBezierTransformer != null) { - matrix = mBezierTransformer.getTransformAtDistance(offset + glyphPoint.x + glyphDelta.x + width / 2); + float halfway = width / 2; + + matrix = mBezierTransformer.getTransformAtDistance( + offset + glyphPoint.x + glyphDelta.x + halfway + ); if (textPathHasReachedEnd()) { break; @@ -155,12 +166,14 @@ private Path getLinePath(Canvas canvas, String line, Paint paint, Path path) { continue; } - matrix.preTranslate(-width / 2, glyphDelta.y); + matrix.preTranslate(-halfway, glyphDelta.y); matrix.postTranslate(0, glyphPoint.y); } else { + matrix = new Matrix(); matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); } + paint.getTextPath(current, 0, 1, 0, 0, glyph); glyphDelta = getGlyphDeltaFromContext(); glyph.transform(matrix); glyphPosition += width; From 44fac691b9585010cc44f8d0a8b5af2e96230215 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Jun 2017 21:47:47 +0300 Subject: [PATCH 023/198] Isolate debugging / diagnostics code. --- .../main/java/com/horcrux/svg/BezierTransformer.java | 2 +- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/BezierTransformer.java b/android/src/main/java/com/horcrux/svg/BezierTransformer.java index 5ae1b542e..8aeb0a44d 100644 --- a/android/src/main/java/com/horcrux/svg/BezierTransformer.java +++ b/android/src/main/java/com/horcrux/svg/BezierTransformer.java @@ -102,7 +102,7 @@ private void setControlPoints() { } } - public float getmStartOffset() { + public float getStartOffset() { return mStartOffset; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 64131c7b8..2997aac11 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -10,7 +10,6 @@ package com.horcrux.svg; -import android.annotation.TargetApi; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; @@ -18,7 +17,6 @@ import android.graphics.PointF; import android.graphics.RectF; import android.graphics.Typeface; -import android.os.Build; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; @@ -103,12 +101,12 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { Path bezierPath; if (mBezierTransformer != null) { - distance = mBezierTransformer.getTotalDistance(); - offset = mBezierTransformer.getmStartOffset(); - textMeasure = paint.measureText(line); - p = mBezierTransformer.getPath(); - bezierPath = p.getPath(); + offset = mBezierTransformer.getStartOffset(); if (debug) { + distance = mBezierTransformer.getTotalDistance(); + textMeasure = paint.measureText(line); + p = mBezierTransformer.getPath(); + bezierPath = p.getPath(); canvas.drawTextOnPath( line, bezierPath, From 0aeb68e90490874196fddcd49f8838f85358a73e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Jun 2017 22:01:58 +0300 Subject: [PATCH 024/198] Fix debugging / diagnostics code. --- android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 2997aac11..d00799902 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -32,7 +32,6 @@ public class TSpanShadowNode extends TextShadowNode { private BezierTransformer mBezierTransformer; private Path mCache; private @Nullable String mContent; - private boolean debug = false; private static final String PROP_FONT_FAMILY = "fontFamily"; private static final String PROP_FONT_SIZE = "fontSize"; @@ -85,6 +84,7 @@ protected Path getPath(Canvas canvas, Paint paint) { } private Path getLinePath(Canvas canvas, String line, Paint paint) { + ReadableMap font = applyTextPropertiesToPaint(paint); int length = line.length(); Path path = new Path(); @@ -102,6 +102,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { if (mBezierTransformer != null) { offset = mBezierTransformer.getStartOffset(); + boolean debug = true; if (debug) { distance = mBezierTransformer.getTotalDistance(); textMeasure = paint.measureText(line); @@ -126,7 +127,6 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { char[] chars = line.toCharArray(); float[] widths = new float[length]; - ReadableMap font = applyTextPropertiesToPaint(paint); double kerningValue = font.getDouble(PROP_KERNING) * mScale; boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); From bd84f09a96f3106aaa0e32f0c5101b39ff06c779 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 22 Jun 2017 17:02:30 +0300 Subject: [PATCH 025/198] Implement correct path measurement, matrix calculation, getTextAnchorShift, startOffset. Add method and spacing attributes to textPath. Correct startOffset calculation. Implement method="stretch". --- .../com/horcrux/svg/BezierTransformer.java | 186 ------------------ .../java/com/horcrux/svg/TSpanShadowNode.java | 111 ++++++----- .../com/horcrux/svg/TextPathShadowNode.java | 30 ++- .../java/com/horcrux/svg/TextShadowNode.java | 49 ++--- elements/TextPath.js | 2 + lib/attributes.js | 2 + lib/extract/extractText.js | 4 + 7 files changed, 111 insertions(+), 273 deletions(-) delete mode 100644 android/src/main/java/com/horcrux/svg/BezierTransformer.java diff --git a/android/src/main/java/com/horcrux/svg/BezierTransformer.java b/android/src/main/java/com/horcrux/svg/BezierTransformer.java deleted file mode 100644 index 8aeb0a44d..000000000 --- a/android/src/main/java/com/horcrux/svg/BezierTransformer.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * 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.Matrix; -import android.graphics.PointF; - -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; - -public class BezierTransformer { - private ReadableArray mBezierCurves; - private PathShadowNode mPath; - private int mCurrentBezierIndex = 0; - private float mStartOffset = 0f; - private float mLastOffset = 0f; - private float mLastRecord = 0f; - private float mLastDistance = 0f; - private PointF mLastPoint = new PointF(); - private PointF mP0 = new PointF(); - private PointF mP1 = new PointF(); - private PointF mP2 = new PointF(); - private PointF mP3 = new PointF(); - private boolean mReachedStart; - private boolean mReachedEnd; - - BezierTransformer(PathShadowNode path, float startOffset) { - mBezierCurves = path.getBezierCurves(); - mStartOffset = startOffset; - mPath = path; - } - - private float calculateBezier(float t, float P0, float P1, float P2, float P3) { - return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3; - } - - private PointF pointAtOffset(float t) { - float x = calculateBezier(t, mP0.x, mP1.x, mP2.x, mP3.x); - float y = calculateBezier(t, mP0.y, mP1.y, mP2.y, mP3.y); - return new PointF(x, y); - } - - private float calculateBezierPrime(float t, float P0, float P1, float P2, float P3) { - return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3; - } - - private float angleAtOffset(float t) { - float dx = calculateBezierPrime(t, mP0.x, mP1.x, mP2.x, mP3.x); - float dy = calculateBezierPrime(t, mP0.y, mP1.y, mP2.y, mP3.y); - return (float)Math.atan2(dy, dx); - } - - private float calculateDistance(PointF a, PointF b) { - return (float)Math.hypot(a.x - b.x, a.y - b.y); - } - - private PointF getPointFromMap(ReadableMap map) { - return new PointF((float)map.getDouble("x"), (float)map.getDouble("y")); - } - - // Simplistic routine to find the offset along Bezier that is - // `distance` away from `point`. `offset` is the offset used to - // generate `point`, and saves us the trouble of recalculating it - // This routine just walks forward until it finds a point at least - // `distance` away. Good optimizations here would reduce the number - // of guesses, but this is tricky since if we go too far out, the - // curve might loop back on leading to incorrect results. Tuning - // kStep is good start. - private float offsetAtDistance(float distance, PointF point, float offset) { - float kStep = 0.001f; // 0.0001 - 0.001 work well - float newDistance = 0; - float newOffset = offset + kStep; - while (newDistance <= distance && newOffset < 1.0) { - newOffset += kStep; - newDistance = calculateDistance(point, pointAtOffset(newOffset)); - } - - mLastDistance = newDistance; - return newOffset; - } - - private void setControlPoints() { - ReadableArray bezier = mBezierCurves.getArray(mCurrentBezierIndex++); - - if (bezier != null) { - // set start point - if (bezier.size() == 1) { - mLastPoint = mP0 = getPointFromMap(bezier.getMap(0)); - setControlPoints(); - } else if (bezier.size() == 3) { - mP1 = getPointFromMap(bezier.getMap(0)); - mP2 = getPointFromMap(bezier.getMap(1)); - mP3 = getPointFromMap(bezier.getMap(2)); - } - } - } - - public float getStartOffset() { - return mStartOffset; - } - - public PathShadowNode getPath() { - return mPath; - } - - public float getTotalDistance() { - float distance = 0; - - while (!mReachedEnd) { - distance += 0.1f; - float offset = offsetAtDistance(distance - mLastRecord, mLastPoint, mLastOffset); - - if (offset < 1) { - PointF glyphPoint = pointAtOffset(offset); - mLastOffset = offset; - mLastPoint = glyphPoint; - mLastRecord = distance; - } else if (mBezierCurves.size() == mCurrentBezierIndex) { - mReachedEnd = true; - } else { - mLastOffset = 0; - mLastPoint = mP0 = mP3; - mLastRecord += mLastDistance; - setControlPoints(); - } - } - - mCurrentBezierIndex = 0; - mLastOffset = 0f; - mLastRecord = 0f; - mLastDistance = 0f; - mLastPoint = new PointF(); - mP0 = new PointF(); - mP1 = new PointF(); - mP2 = new PointF(); - mP3 = new PointF(); - mReachedEnd = false; - - return distance; - } - - public Matrix getTransformAtDistance(float distance) { - mReachedStart = distance >= 0; - - if (mReachedEnd || !mReachedStart) { - return new Matrix(); - } - - float offset = offsetAtDistance(distance - mLastRecord, mLastPoint, mLastOffset); - - if (offset < 1) { - PointF glyphPoint = pointAtOffset(offset); - mLastOffset = offset; - mLastPoint = glyphPoint; - mLastRecord = distance; - Matrix matrix = new Matrix(); - matrix.setRotate((float)Math.toDegrees(angleAtOffset(offset))); - matrix.postTranslate(glyphPoint.x, glyphPoint.y); - return matrix; - } else if (mBezierCurves.size() == mCurrentBezierIndex) { - mReachedEnd = true; - return new Matrix(); - } else { - mLastOffset = 0; - mLastPoint = mP0 = mP3; - mLastRecord += mLastDistance; - setControlPoints(); - return getTransformAtDistance(distance); - } - } - - public boolean hasReachedEnd() { - return mReachedEnd; - } - - public boolean hasReachedStart() { - return mReachedStart; - } -} diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index d00799902..afc98d000 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -14,6 +14,7 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PathMeasure; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.Typeface; @@ -24,14 +25,17 @@ import javax.annotation.Nullable; +import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG; +import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG; + /** * Shadow node for virtual TSpan view */ public class TSpanShadowNode extends TextShadowNode { - private BezierTransformer mBezierTransformer; private Path mCache; private @Nullable String mContent; + private TextPathShadowNode textPath; private static final String PROP_FONT_FAMILY = "fontFamily"; private static final String PROP_FONT_SIZE = "fontSize"; @@ -83,6 +87,21 @@ protected Path getPath(Canvas canvas, Paint paint) { return path; } + private float getTextAnchorShift(float width) { + float x = 0; + + switch (getComputedTextAnchor()) { + case TEXT_ANCHOR_MIDDLE: + x = -width / 2; + break; + case TEXT_ANCHOR_END: + x = -width; + break; + } + + return x; + } + private Path getLinePath(Canvas canvas, String line, Paint paint) { ReadableMap font = applyTextPropertiesToPaint(paint); int length = line.length(); @@ -92,29 +111,22 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { return path; } - PointF glyphPoint = getGlyphPointFromContext(0, 0); - PointF glyphDelta = getGlyphDeltaFromContext(); - float textMeasure = 0; - float distance = 0; float offset = 0; - PathShadowNode p; - Path bezierPath; - - if (mBezierTransformer != null) { - offset = mBezierTransformer.getStartOffset(); - boolean debug = true; - if (debug) { - distance = mBezierTransformer.getTotalDistance(); - textMeasure = paint.measureText(line); - p = mBezierTransformer.getPath(); - bezierPath = p.getPath(); - canvas.drawTextOnPath( - line, - bezierPath, - offset + glyphPoint.x + glyphDelta.x, - glyphDelta.y, - paint - ); + float distance = 0; + float renderMethodScaling = 1; + float textMeasure = paint.measureText(line); + float textAnchorShift = getTextAnchorShift(textMeasure); + + PathMeasure pm = null; + + if (textPath != null) { + pm = new PathMeasure(textPath.getPath(), false); + distance = pm.getLength(); + offset = PropHelper.fromPercentageToFloat(textPath.getStartOffset(), distance, 0, mScale); + String spacing = textPath.getSpacing(); // spacing = "auto | exact" + String method = textPath.getMethod(); // method = "align | stretch" + if ("stretch".equals(method)) { + renderMethodScaling = distance / textMeasure; } } @@ -122,6 +134,8 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { float width; Matrix matrix; String current; + PointF glyphPoint; + PointF glyphDelta; String previous = ""; float glyphPosition = 0; char[] chars = line.toCharArray(); @@ -133,8 +147,8 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { paint.getTextWidths(line, widths); for (int index = 0; index < length; index++) { + width = widths[index] * renderMethodScaling; current = String.valueOf(chars[index]); - width = widths[index]; glyph = new Path(); if (isKerningValueSet) { @@ -149,32 +163,42 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { previous = current; } - glyphPoint = getGlyphPointFromContext(glyphPosition, width); + glyphPoint = getGlyphPointFromContext(textAnchorShift + glyphPosition, width); + glyphDelta = getGlyphDeltaFromContext(); + glyphPosition += width; + matrix = new Matrix(); - if (mBezierTransformer != null) { + if (textPath != null) { float halfway = width / 2; - - matrix = mBezierTransformer.getTransformAtDistance( - offset + glyphPoint.x + glyphDelta.x + halfway - ); - - if (textPathHasReachedEnd()) { - break; - } else if (!textPathHasReachedStart()) { + float start = offset + glyphPoint.x + glyphDelta.x; + float midpoint = start + halfway; + + if (midpoint > distance ) { + if (start <= distance) { + // Seems to cut off too early, see e.g. toap3, this shows the last "p" + midpoint = start; + halfway = 0; + } else { + break; + } + } else if (midpoint < 0) { continue; } + pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); + matrix.preTranslate(-halfway, glyphDelta.y); + matrix.preScale(renderMethodScaling, 1); matrix.postTranslate(0, glyphPoint.y); } else { - matrix = new Matrix(); - matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y); + matrix.setTranslate( + glyphPoint.x + glyphDelta.x + textAnchorShift, + glyphPoint.y + glyphDelta.y + ); } paint.getTextPath(current, 0, 1, 0, 0, glyph); - glyphDelta = getGlyphDeltaFromContext(); glyph.transform(matrix); - glyphPosition += width; path.addPath(glyph); } @@ -218,8 +242,7 @@ private void setupTextPath() { while (parent != null) { if (parent.getClass() == TextPathShadowNode.class) { - TextPathShadowNode textPath = (TextPathShadowNode)parent; - mBezierTransformer = textPath.getBezierTransformer(); + textPath = (TextPathShadowNode)parent; break; } else if (!(parent instanceof TextShadowNode)) { break; @@ -228,12 +251,4 @@ private void setupTextPath() { parent = parent.getParent(); } } - - private boolean textPathHasReachedEnd() { - return mBezierTransformer.hasReachedEnd(); - } - - private boolean textPathHasReachedStart() { - return mBezierTransformer.hasReachedStart(); - } } diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index fb6221c66..9e07d9b02 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -23,6 +23,8 @@ public class TextPathShadowNode extends TextShadowNode { private String mHref; + private String mMethod; + private String mSpacing; private @Nullable String mStartOffset; @ReactProp(name = "href") @@ -37,12 +39,36 @@ public void setStartOffset(@Nullable String startOffset) { markUpdated(); } + @ReactProp(name = "method") + public void setMethod(@Nullable String method) { + mMethod = method; + markUpdated(); + } + + @ReactProp(name = "spacing") + public void setSpacing(@Nullable String spacing) { + mSpacing = spacing; + markUpdated(); + } + + public String getMethod() { + return mMethod; + } + + public String getSpacing() { + return mSpacing; + } + + public String getStartOffset() { + return mStartOffset; + } + @Override public void draw(Canvas canvas, Paint paint, float opacity) { drawGroup(canvas, paint, opacity); } - public BezierTransformer getBezierTransformer() { + public Path getPath() { SvgViewShadowNode svg = getSvgShadowNode(); VirtualNode template = svg.getDefinedTemplate(mHref); @@ -52,7 +78,7 @@ public BezierTransformer getBezierTransformer() { } PathShadowNode path = (PathShadowNode)template; - return new BezierTransformer(path, relativeOnWidth(mStartOffset)); + return path.getPath(); } @Override diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 651318f31..b0ad091d0 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -33,10 +33,10 @@ public class TextShadowNode extends GroupShadowNode { - private static final int TEXT_ANCHOR_AUTO = 0; - private static final int TEXT_ANCHOR_START = 1; - private static final int TEXT_ANCHOR_MIDDLE = 2; - private static final int TEXT_ANCHOR_END = 3; + static final int TEXT_ANCHOR_AUTO = 0; + static final int TEXT_ANCHOR_START = 1; + static final int TEXT_ANCHOR_MIDDLE = 2; + static final int TEXT_ANCHOR_END = 3; private int mTextAnchor = TEXT_ANCHOR_AUTO; private @Nullable ReadableArray mDeltaX; @@ -86,8 +86,6 @@ public void draw(Canvas canvas, Paint paint, float opacity) { setupGlyphContext(); clip(canvas, paint); Path path = getGroupPath(canvas, paint); - Matrix matrix = getAlignMatrix(path); - canvas.concat(matrix); drawGroup(canvas, paint, opacity); releaseCachedPath(); } @@ -97,9 +95,6 @@ public void draw(Canvas canvas, Paint paint, float opacity) { protected Path getPath(Canvas canvas, Paint paint) { setupGlyphContext(); Path groupPath = getGroupPath(canvas, paint); - Matrix matrix = getAlignMatrix(groupPath); - groupPath.transform(matrix); - releaseCachedPath(); return groupPath; } @@ -108,20 +103,21 @@ private int getTextAnchor() { return mTextAnchor; } - private int getComputedTextAnchor() { + int getComputedTextAnchor() { int anchor = mTextAnchor; ReactShadowNode shadowNode = this; - while (shadowNode.getChildCount() > 0 && - anchor == TEXT_ANCHOR_AUTO) { - shadowNode = shadowNode.getChildAt(0); - + while (shadowNode instanceof GroupShadowNode) { if (shadowNode instanceof TextShadowNode) { anchor = ((TextShadowNode) shadowNode).getTextAnchor(); - } else { - break; + if (anchor != TEXT_ANCHOR_AUTO) { + break; + } } + + shadowNode = shadowNode.getParent(); } + return anchor; } @@ -147,25 +143,4 @@ protected Path getGroupPath(Canvas canvas, Paint paint) { protected void pushGlyphContext() { getTextRoot().getGlyphContext().pushContext(mFont, mDeltaX, mDeltaY, mPositionX, mPositionY); } - - private Matrix getAlignMatrix(Path path) { - RectF box = new RectF(); - path.computeBounds(box, true); - - float width = box.width(); - float x = 0; - - switch (getComputedTextAnchor()) { - case TEXT_ANCHOR_MIDDLE: - x = -width / 2; - break; - case TEXT_ANCHOR_END: - x = -width; - break; - } - - Matrix matrix = new Matrix(); - matrix.setTranslate(x, 0); - return matrix; - } } diff --git a/elements/TextPath.js b/elements/TextPath.js index 837749948..236bb461e 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -16,6 +16,8 @@ export default class extends Shape { ...pathProps, ...fontProps, href: PropTypes.string.isRequired, + method: PropTypes.oneOf(['align', 'stretch']), + spacing: PropTypes.oneOf(['auto', 'exact']), startOffset: numberProp }; diff --git a/lib/attributes.js b/lib/attributes.js index 83c7c31ca..68cb285ac 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -114,6 +114,8 @@ const TextAttributes = { const TextPathAttributes = { href: true, + method: true, + spacing: true, startOffset: true, ...RenderableAttributes }; diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 17fca9d79..e9dea6f2e 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -91,6 +91,8 @@ export default function(props, container) { y, dx, dy, + method, + spacing, textAnchor, startOffset } = props; @@ -127,6 +129,8 @@ export default function(props, container) { content, deltaX, deltaY, + method, + spacing, startOffset: (startOffset || 0).toString(), positionX: _.isNil(x) ? null : x.toString(), positionY: _.isNil(y) ? null : y.toString() From d4158d09dd168122c178194fe0900bd41f545029 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 22 Jun 2017 17:36:41 +0300 Subject: [PATCH 026/198] Simplify and optimize glyphPosition calculation --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index afc98d000..0e625b861 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -111,8 +111,8 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { return path; } - float offset = 0; float distance = 0; + float startOffset = 0; float renderMethodScaling = 1; float textMeasure = paint.measureText(line); float textAnchorShift = getTextAnchorShift(textMeasure); @@ -122,8 +122,8 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - offset = PropHelper.fromPercentageToFloat(textPath.getStartOffset(), distance, 0, mScale); - String spacing = textPath.getSpacing(); // spacing = "auto | exact" + startOffset = PropHelper.fromPercentageToFloat(textPath.getStartOffset(), distance, 0, mScale); + // String spacing = textPath.getSpacing(); // spacing = "auto | exact" String method = textPath.getMethod(); // method = "align | stretch" if ("stretch".equals(method)) { renderMethodScaling = distance / textMeasure; @@ -137,9 +137,9 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { PointF glyphPoint; PointF glyphDelta; String previous = ""; - float glyphPosition = 0; char[] chars = line.toCharArray(); float[] widths = new float[length]; + float glyphPosition = startOffset + textAnchorShift; double kerningValue = font.getDouble(PROP_KERNING) * mScale; boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); @@ -163,19 +163,20 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { previous = current; } - glyphPoint = getGlyphPointFromContext(textAnchorShift + glyphPosition, width); + glyphPoint = getGlyphPointFromContext(glyphPosition, width); glyphDelta = getGlyphDeltaFromContext(); glyphPosition += width; matrix = new Matrix(); if (textPath != null) { float halfway = width / 2; - float start = offset + glyphPoint.x + glyphDelta.x; + float start = glyphPoint.x + glyphDelta.x; float midpoint = start + halfway; if (midpoint > distance ) { if (start <= distance) { // Seems to cut off too early, see e.g. toap3, this shows the last "p" + // Tangent will be from start position instead of midpoint midpoint = start; halfway = 0; } else { From f7f190924968c1ee4a899fa6e4895534ebf872f8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 26 Jun 2017 20:44:45 +0300 Subject: [PATCH 027/198] Implement support for custom fonts. Implement support for fontSize value without unit identifier, processed as height value in the current user coordinate system. https://www.w3.org/TR/SVG11/text.html#FontSizeProperty --- .../com/horcrux/svg/SvgViewShadowNode.java | 8 ++++- .../java/com/horcrux/svg/TSpanShadowNode.java | 29 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index 291e7c401..e76c6cae3 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -17,6 +17,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; +import android.support.annotation.NonNull; import android.util.Base64; import com.facebook.react.uimanager.DisplayMetricsHolder; @@ -128,7 +129,7 @@ public Rect getCanvasBounds() { private void drawChildren(Canvas canvas) { if (mAlign != null) { - RectF vbRect = new RectF(mMinX * mScale, mMinY * mScale, (mMinX + mVbWidth) * mScale, (mMinY + mVbHeight) * mScale); + RectF vbRect = getViewBox(); RectF eRect = new RectF(0, 0, getLayoutWidth(), getLayoutHeight()); mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice, false); canvas.concat(mViewBoxMatrix); @@ -159,6 +160,11 @@ private void drawChildren(Canvas canvas) { } } + @NonNull + RectF getViewBox() { + return new RectF(mMinX * mScale, mMinY * mScale, (mMinX + mVbWidth) * mScale, (mMinY + mVbHeight) * mScale); + } + public String toDataURL() { Bitmap bitmap = Bitmap.createBitmap( (int) getLayoutWidth(), diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 0e625b861..cec9266a0 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -10,6 +10,7 @@ package com.horcrux.svg; +import android.content.res.AssetManager; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; @@ -211,8 +212,13 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint) { paint.setTextAlign(Paint.Align.LEFT); - float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale; - float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale; + RectF vb = getSvgShadowNode().getViewBox(); + float height = vb.height(); + float ch = getCanvasHeight(); + float heightScale = height / ch; + + float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale * heightScale; + float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale * heightScale; paint.setTextSize(fontSize); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { @@ -232,8 +238,25 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint) { } else { fontStyle = Typeface.NORMAL; } + + AssetManager a = getThemedContext().getResources().getAssets(); + + Typeface tf = null; + try { + tf = Typeface.createFromAsset(a, "fonts/" + font.getString(PROP_FONT_FAMILY) + ".otf"); + } catch (Exception ignored) { + try { + tf = Typeface.createFromAsset(a, "fonts/" + font.getString(PROP_FONT_FAMILY) + ".ttf"); + } catch (Exception ignored2) { + try { + tf = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle); + } catch (Exception ignored3) { + } + } + } + // NB: if the font family is null / unsupported, the default one will be used - paint.setTypeface(Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle)); + paint.setTypeface(tf); return font; } From c7039588f6a96b076c5dd924d01577a8467c580f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 27 Jun 2017 20:45:04 +0300 Subject: [PATCH 028/198] Improve coordinate system calculations for font-size, letter-spacing etc. Fix parsing of transform to account for single parameter scale and translate correctly. Fix skewX, had opposite of intended effect. https://www.w3.org/TR/SVG/coords.html https://www.w3.org/TR/SVG2/coords.html https://www.w3.org/TR/SVGTiny12/coords.html https://www.w3.org/TR/SVG/coords.html#TransformAttribute https://www.w3.org/TR/SVG/coords.html#SkewXDefined --- .../java/com/horcrux/svg/TSpanShadowNode.java | 16 ++++++++++-- .../java/com/horcrux/svg/VirtualNode.java | 26 +++++++++++++++++++ lib/Matrix2D.js | 2 +- lib/attributes.js | 2 ++ lib/extract/extractProps.js | 19 +++++++++++++- lib/extract/extractTransform.js | 8 +++--- 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index cec9266a0..219616ce3 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -104,7 +104,7 @@ private float getTextAnchorShift(float width) { } private Path getLinePath(Canvas canvas, String line, Paint paint) { - ReadableMap font = applyTextPropertiesToPaint(paint); + ReadableMap font = applyTextPropertiesToPaint(paint, canvas); int length = line.length(); Path path = new Path(); @@ -207,7 +207,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { return path; } - private ReadableMap applyTextPropertiesToPaint(Paint paint) { + private ReadableMap applyTextPropertiesToPaint(Paint paint, Canvas canvas) { ReadableMap font = getFontFromContext(); paint.setTextAlign(Paint.Align.LEFT); @@ -217,6 +217,18 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint) { float ch = getCanvasHeight(); float heightScale = height / ch; + SvgViewShadowNode svg = getSvgShadowNode(); + ReactShadowNode node = this; + while (node != null && !node.equals(svg)) { + + if (node instanceof VirtualNode) { + VirtualNode v = ((VirtualNode) node); + heightScale /= v.getScaleY(); + } + + node = node.getParent(); + } + float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale * heightScale; float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale * heightScale; diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index a2635e8ce..c0737b9d0 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -33,6 +33,8 @@ public abstract class VirtualNode extends LayoutShadowNode { private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; protected float mOpacity = 1f; + protected float mScaleX = 1f; + protected float mScaleY = 1f; protected Matrix mMatrix = new Matrix(); private int mClipRule; @@ -109,6 +111,18 @@ public void setOpacity(float opacity) { markUpdated(); } + @ReactProp(name = "scaleX", defaultFloat = 1f) + public void setScaleX(float scaleX) { + mScaleX = scaleX; + markUpdated(); + } + + @ReactProp(name = "scaleY", defaultFloat = 1f) + public void setScaleY(float scaleY) { + mScaleY = scaleY; + markUpdated(); + } + @ReactProp(name = "matrix") public void setMatrix(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { @@ -134,6 +148,18 @@ public void setMatrix(@Nullable ReadableArray matrixArray) { markUpdated(); } + public Matrix getMatrix() { + return mMatrix; + } + + public float getScaleX() { + return mScaleX; + } + + public float getScaleY() { + return mScaleY; + } + @ReactProp(name = "responsible", defaultBoolean = false) public void setResponsible(boolean responsible) { mResponsible = responsible; diff --git a/lib/Matrix2D.js b/lib/Matrix2D.js index e97607966..1fba91c0b 100644 --- a/lib/Matrix2D.js +++ b/lib/Matrix2D.js @@ -215,7 +215,7 @@ export default class Matrix2D { // TODO: can this be combined into a single append operation? skewX *= DEG_TO_RAD; skewY *= DEG_TO_RAD; - this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); + this.append(Math.cos(skewY), Math.sin(skewY), Math.sin(skewX), Math.cos(skewX), x, y); this.append(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, 0, 0); } else { this.append(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, x, y); diff --git a/lib/attributes.js b/lib/attributes.js index 68cb285ac..5185a53e1 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -42,6 +42,8 @@ const NodeAttributes = { matrix: { diff: arrayDiffer }, + scaleX: true, + scaleY: true, opacity: true, clipRule: true, clipPath: true, diff --git a/lib/extract/extractProps.js b/lib/extract/extractProps.js index c632eb63c..1fd574b00 100644 --- a/lib/extract/extractProps.js +++ b/lib/extract/extractProps.js @@ -1,6 +1,6 @@ import extractFill from './extractFill'; import extractStroke from './extractStroke'; -import extractTransform from './extractTransform'; +import extractTransform, {props2transform, tp} from './extractTransform'; import extractClipPath from './extractClipPath'; import extractResponder from './extractResponder'; import extractOpacity from './extractOpacity'; @@ -26,6 +26,23 @@ export default function(props, ref) { extractedProps.matrix = extractTransform(props); + Object.assign(extractedProps, props2transform(props)); + let transform = props.transform; + if (transform) { + if (typeof transform === 'string') { + var transformParsed = tp.parse(transform); + if (transformParsed.matrix) { + // TODO: Extract scaling values for coordinate system + // Especially scaleY for calculating scaling of fontSize + } else { + let trans = props2transform(transformParsed); + if (typeof trans === 'object') { + Object.assign(extractedProps, trans); + } + } + } + } + Object.assign(extractedProps, extractResponder(props, ref)); return extractedProps; diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index afaa8aeb7..1b0ab5350 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -50,11 +50,11 @@ class TransformParser { break; case 'translate': retval.translateX = transLst[i + 1]; - retval.translateY = (i + 2 <= transLst.length) ? transLst[i + 2] : 0; + retval.translateY = (3 === transLst.length) ? transLst[i + 2] : 0; break; case 'scale': retval.scaleX = transLst[i + 1]; - retval.scaleY = (i + 2 <= transLst.length) ? transLst[i + 2] : retval.scaleX; + retval.scaleY = (3 === transLst.length) ? transLst[i + 2] : retval.scaleX; break; case 'rotate': retval.rotation = transLst[i + 1]; @@ -85,7 +85,7 @@ class TransformParser { } } -const tp = new TransformParser(); +export const tp = new TransformParser(); function appendTransform(transform) { @@ -147,7 +147,7 @@ function universal2axis(universal, axisX, axisY, defaultValue) { return [x || defaultValue || 0, y || defaultValue || 0]; } -function props2transform(props) { +export function props2transform(props) { if (props && (typeof props === 'string')) { return props; } From 113b1ec597ca5501e0ae233265d398e6f64ef7a3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 15 Jul 2017 14:37:31 +0300 Subject: [PATCH 029/198] Revert "fixed issue #397" This reverts commit 26bbc1c3180a9486ad16cc5a5be96e02eb705479. --- android/src/main/java/com/horcrux/svg/SvgPackage.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 278ab13a7..716a8da3c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -44,6 +44,11 @@ public List createViewManagers(ReactApplicationContext reactContext new SvgViewManager()); } + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + @Override public List createNativeModules(ReactApplicationContext reactContext) { return Collections.singletonList(new SvgViewModule(reactContext)); From c76ce3c7806e49c78007ee90c7c6032ff53d8239 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 15 Jul 2017 17:13:39 +0300 Subject: [PATCH 030/198] Fix remaining React.PropTypes to use import PropTypes from 'prop-types'; --- elements/TSpan.js | 4 ++-- elements/Text.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elements/TSpan.js b/elements/TSpan.js index 391c01a03..4f97de410 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -20,7 +20,7 @@ export default class extends Shape { }; static childContextTypes = { - isInAParentText: React.PropTypes.bool + isInAParentText: PropTypes.bool }; getChildContext() { @@ -31,7 +31,7 @@ export default class extends Shape { getContextTypes() { return { - isInAParentText: React.PropTypes.bool + isInAParentText: PropTypes.bool }; } diff --git a/elements/Text.js b/elements/Text.js index 6d30d3a76..60f86dd11 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -19,7 +19,7 @@ export default class extends Shape { }; static childContextTypes = { - isInAParentText: React.PropTypes.bool + isInAParentText: PropTypes.bool }; getChildContext() { @@ -30,7 +30,7 @@ export default class extends Shape { getContextTypes() { return { - isInAParentText: React.PropTypes.bool + isInAParentText: PropTypes.bool }; } From 687a2e1776b85a6413b7f9a75f547b07e966f3a7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 15 Jul 2017 18:11:05 +0300 Subject: [PATCH 031/198] Fix textAnchorShift being applied twice for normal text/tspan (when not using text on a path) --- android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 219616ce3..00530c40a 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -194,7 +194,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { matrix.postTranslate(0, glyphPoint.y); } else { matrix.setTranslate( - glyphPoint.x + glyphDelta.x + textAnchorShift, + glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y ); } From 61a17cb120bfed061f2ab6cf40491a266c13d964 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 15 Jul 2017 23:35:59 +0300 Subject: [PATCH 032/198] Fix last known rendering issues. Remove redundant code as other bugs are fixed. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 00530c40a..8b51648e5 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -212,25 +212,8 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint, Canvas canvas) { paint.setTextAlign(Paint.Align.LEFT); - RectF vb = getSvgShadowNode().getViewBox(); - float height = vb.height(); - float ch = getCanvasHeight(); - float heightScale = height / ch; - - SvgViewShadowNode svg = getSvgShadowNode(); - ReactShadowNode node = this; - while (node != null && !node.equals(svg)) { - - if (node instanceof VirtualNode) { - VirtualNode v = ((VirtualNode) node); - heightScale /= v.getScaleY(); - } - - node = node.getParent(); - } - - float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale * heightScale; - float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale * heightScale; + float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale; + float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale; paint.setTextSize(fontSize); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { From 2458a2f2620a91ff3b68cbd200fd9666bf785229 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 16 Jul 2017 01:50:59 +0300 Subject: [PATCH 033/198] Implement rotate and textDecoration --- .../java/com/horcrux/svg/GlyphContext.java | 63 +++++++++++++++++-- .../java/com/horcrux/svg/GroupShadowNode.java | 4 ++ .../java/com/horcrux/svg/TSpanShadowNode.java | 9 +++ .../java/com/horcrux/svg/TextShadowNode.java | 48 +++++++++++++- lib/attributes.js | 2 + lib/extract/extractText.js | 14 ++++- 6 files changed, 131 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 1fe3468f4..10c529fa7 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -28,10 +28,13 @@ public class GlyphContext { private ArrayList mDeltaContext; private ArrayList> mDeltaXContext; private ArrayList> mDeltaYContext; + private ArrayList> mRotationContext; + private ArrayList mGlyphRotationContext; private ArrayList mXContext; private ArrayList mYContext; private @Nonnull PointF mCurrentLocation; private @Nonnull PointF mCurrentDelta; + private float mRotation = 0; private float mScale; private float mWidth; private float mHeight; @@ -51,6 +54,8 @@ public class GlyphContext { mDeltaContext = new ArrayList<>(); mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); + mRotationContext = new ArrayList<>(); + mGlyphRotationContext = new ArrayList<>(); mXContext = new ArrayList<>(); mYContext = new ArrayList<>(); } @@ -66,6 +71,8 @@ public void pushContext(@Nullable ReadableMap font) { mFontContext.add(font); mDeltaXContext.add(new ArrayList()); mDeltaYContext.add(new ArrayList()); + mRotationContext.add(new ArrayList()); + mGlyphRotationContext.add(mRotation); mXContext.add(location.x); mYContext.add(location.y); @@ -73,7 +80,7 @@ public void pushContext(@Nullable ReadableMap font) { mContextLength++; } - public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { + public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { PointF location = mCurrentLocation; mDeltaContext.add(mCurrentDelta); @@ -94,6 +101,8 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray delt mFontContext.add(font); mDeltaXContext.add(getFloatArrayListFromReadableArray(deltaX)); mDeltaYContext.add(getFloatArrayListFromReadableArray(deltaY)); + mRotationContext.add(getFloatArrayListFromReadableArray(rotate)); + mGlyphRotationContext.add(mRotation); mXContext.add(location.x); mYContext.add(location.y); @@ -104,11 +113,14 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray delt public void popContext() { float x = mXContext.get(mContextLength - 1); float y = mYContext.get(mContextLength - 1); + float r = mGlyphRotationContext.get(mContextLength - 1); mFontContext.remove(mContextLength - 1); mLocationContext.remove(mContextLength - 1); mDeltaContext.remove(mContextLength - 1); mDeltaXContext.remove(mContextLength - 1); mDeltaYContext.remove(mContextLength - 1); + mRotationContext.remove(mContextLength - 1); + mGlyphRotationContext.remove(mContextLength - 1); mXContext.remove(mContextLength - 1); mYContext.remove(mContextLength - 1); @@ -117,12 +129,14 @@ public void popContext() { if (mContextLength != 0) { mXContext.set(mContextLength - 1, x); mYContext.set(mContextLength - 1, y); + mGlyphRotationContext.set(mContextLength - 1, r); PointF lastLocation = mLocationContext.get(mContextLength - 1); PointF lastDelta = mDeltaContext.get(mContextLength - 1); mCurrentLocation = clonePointF(lastLocation); mCurrentDelta = clonePointF(lastDelta); mCurrentLocation.x = lastLocation.x = x; mCurrentLocation.y = lastLocation.y = y; + mRotation = r; } } @@ -134,8 +148,8 @@ public PointF getNextGlyphPoint(float offset, float glyphWidth) { } public PointF getNextGlyphDelta() { - float dx = mScale * getNextDelta(mDeltaXContext); - float dy = mScale * getNextDelta(mDeltaYContext); + float dx = mScale * getNextFloat(mDeltaXContext); + float dy = mScale * getNextFloat(mDeltaYContext); if (mContextLength > 0) { for (PointF point: mDeltaContext) { @@ -149,13 +163,50 @@ public PointF getNextGlyphDelta() { return new PointF(dx, dy); } - private float getNextDelta(ArrayList> deltaContext) { - float value = 0; + public float getNextGlyphRotation() { + if (hasNextFloat(mRotationContext)) { + float r = getNextFloat(mRotationContext); + + if (mContextLength > 0) { + for (int i = 0; i < mContextLength; i++) { + mGlyphRotationContext.set(i, r); + } + + return mGlyphRotationContext.get(mContextLength - 1); + } + + return r; + } else if (mContextLength > 0) { + return mGlyphRotationContext.get(mContextLength - 1); + } + + return 0; + } + + private float getNextFloat(ArrayList> context) { + return getNextFloat(context, 0); + } + + private boolean hasNextFloat(ArrayList> context) { + int index = mContextLength - 1; + + for (; index >= 0; index--) { + ArrayList delta = context.get(index); + + if (delta.size() != 0) { + return true; + } + } + + return false; + } + + private float getNextFloat(ArrayList> context, float value) { boolean valueSet = false; int index = mContextLength - 1; for (; index >= 0; index--) { - ArrayList delta = deltaContext.get(index); + ArrayList delta = context.get(index); if (delta.size() != 0) { if (!valueSet) { diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index ea48a6e54..65de56fc8 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -101,6 +101,10 @@ protected PointF getGlyphDeltaFromContext() { return getTextRoot().getGlyphContext().getNextGlyphDelta(); } + protected float getNextGlyphRotationFromContext() { + return getTextRoot().getGlyphContext().getNextGlyphRotation(); + } + public void draw(final Canvas canvas, final Paint paint, final float opacity) { setupGlyphContext(); if (opacity > MIN_OPACITY_FOR_DRAW) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 8b51648e5..52f25a3f6 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -137,6 +137,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { String current; PointF glyphPoint; PointF glyphDelta; + float glyphRotation; String previous = ""; char[] chars = line.toCharArray(); float[] widths = new float[length]; @@ -166,6 +167,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { glyphPoint = getGlyphPointFromContext(glyphPosition, width); glyphDelta = getGlyphDeltaFromContext(); + glyphRotation = getNextGlyphRotationFromContext(); glyphPosition += width; matrix = new Matrix(); @@ -199,6 +201,8 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { ); } + matrix.preRotate(glyphRotation); + paint.getTextPath(current, 0, 1, 0, 0, glyph); glyph.transform(matrix); path.addPath(glyph); @@ -220,6 +224,11 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint, Canvas canvas) { paint.setLetterSpacing(letterSpacing / fontSize); // setLetterSpacing is only available from LOLLIPOP and on } + int decoration = getTextDecoration(); + + paint.setUnderlineText(decoration == TEXT_DECORATION_UNDERLINE); + paint.setStrikeThruText(decoration == TEXT_DECORATION_LINE_THROUGH); + 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)); diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index b0ad091d0..e0e767fa0 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -38,7 +38,15 @@ public class TextShadowNode extends GroupShadowNode { static final int TEXT_ANCHOR_MIDDLE = 2; static final int TEXT_ANCHOR_END = 3; + static final int TEXT_DECORATION_NONE = 0; + static final int TEXT_DECORATION_UNDERLINE = 1; + static final int TEXT_DECORATION_OVERLINE = 2; + static final int TEXT_DECORATION_LINE_THROUGH = 3; + static final int TEXT_DECORATION_BLINK = 4; + private int mTextAnchor = TEXT_ANCHOR_AUTO; + private int mTextDecoration = TEXT_DECORATION_NONE; + private @Nullable ReadableArray mRotate; private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaY; private @Nullable String mPositionX; @@ -50,6 +58,18 @@ public void setTextAnchor(int textAnchor) { markUpdated(); } + @ReactProp(name = "textDecoration", defaultInt = TEXT_DECORATION_NONE) + public void setTextDecoration(int textDecoration) { + mTextDecoration = textDecoration; + markUpdated(); + } + + @ReactProp(name = "rotate") + public void setRotate(@Nullable ReadableArray rotate) { + mRotate = rotate; + markUpdated(); + } + @ReactProp(name = "deltaX") public void setDeltaX(@Nullable ReadableArray deltaX) { mDeltaX = deltaX; @@ -103,9 +123,33 @@ private int getTextAnchor() { return mTextAnchor; } + int getTextDecoration() { + int decoration = mTextDecoration; + if (decoration != TEXT_DECORATION_NONE) { + return decoration; + } + ReactShadowNode shadowNode = this.getParent(); + + while (shadowNode instanceof GroupShadowNode) { + if (shadowNode instanceof TextShadowNode) { + decoration = ((TextShadowNode) shadowNode).getTextDecoration(); + if (decoration != TEXT_DECORATION_NONE) { + break; + } + } + + shadowNode = shadowNode.getParent(); + } + + return decoration; + } + int getComputedTextAnchor() { int anchor = mTextAnchor; - ReactShadowNode shadowNode = this; + if (anchor != TEXT_ANCHOR_AUTO) { + return anchor; + } + ReactShadowNode shadowNode = this.getParent(); while (shadowNode instanceof GroupShadowNode) { if (shadowNode instanceof TextShadowNode) { @@ -141,6 +185,6 @@ protected Path getGroupPath(Canvas canvas, Paint paint) { @Override protected void pushGlyphContext() { - getTextRoot().getGlyphContext().pushContext(mFont, mDeltaX, mDeltaY, mPositionX, mPositionY); + getTextRoot().getGlyphContext().pushContext(mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY); } } diff --git a/lib/attributes.js b/lib/attributes.js index 5185a53e1..27c8dbedb 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -107,8 +107,10 @@ const TextAttributes = { diff: fontDiffer }, textAnchor: true, + textDecoration: true, deltaX: arrayDiffer, deltaY: arrayDiffer, + rotate: arrayDiffer, positionX: true, positionY: true, ...RenderableAttributes diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index e9dea6f2e..6ec3bb643 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -15,6 +15,13 @@ const anchors = { end: 3 }; +const decorations = { + none: 0, + underline: 1, + overline: 2, + 'line-through': 3, + blink: 4 +}; let cachedFontObjectsFromString = {}; function extractSingleFontFamily(fontFamilyString) { @@ -91,15 +98,18 @@ export default function(props, container) { y, dx, dy, + rotate, method, spacing, textAnchor, - startOffset + startOffset, + textDecoration } = props; const deltaX = parseDelta(dx); const deltaY = parseDelta(dy); + const rotates = parseDelta(rotate); let { children } = props; let content = null; @@ -123,12 +133,14 @@ export default function(props, container) { } return { + textDecoration: decorations[textDecoration] || 0, textAnchor: anchors[textAnchor] || 0, font: extractFont(props), children, content, deltaX, deltaY, + rotate: rotates, method, spacing, startOffset: (startOffset || 0).toString(), From 8e157911073417a60114762414601ef9cf43383d Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 16 Jul 2017 02:01:50 +0300 Subject: [PATCH 034/198] Fix extractTransform not to interpret text rotate attribute and transform rotation --- lib/extract/extractTransform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index 1b0ab5350..fb097eb2a 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -161,7 +161,7 @@ export function props2transform(props) { ); return { - rotation: +props.rotation || +props.rotate || 0, + rotation: +props.rotation || 0, scaleX: scaleX, scaleY: scaleY, originX: originX, From a1fca0990aad3985fcf040b9cdced7ba3039424a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 16 Jul 2017 03:22:17 +0300 Subject: [PATCH 035/198] Implement support for whitespace separated list of x or y values --- .../java/com/horcrux/svg/GlyphContext.java | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 10c529fa7..859b49b49 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -17,6 +17,7 @@ import com.facebook.react.bridge.WritableMap; import java.util.ArrayList; +import java.util.Arrays; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -26,6 +27,8 @@ public class GlyphContext { private ArrayList mFontContext; private ArrayList mLocationContext; private ArrayList mDeltaContext; + private ArrayList> mXPositionsContext; + private ArrayList> mYPositionsContext; private ArrayList> mDeltaXContext; private ArrayList> mDeltaYContext; private ArrayList> mRotationContext; @@ -52,6 +55,8 @@ public class GlyphContext { mFontContext = new ArrayList<>(); mLocationContext = new ArrayList<>(); mDeltaContext = new ArrayList<>(); + mXPositionsContext = new ArrayList<>(); + mYPositionsContext = new ArrayList<>(); mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); mRotationContext = new ArrayList<>(); @@ -69,6 +74,8 @@ public void pushContext(@Nullable ReadableMap font) { mLocationContext.add(location); mFontContext.add(font); + mXPositionsContext.add(new ArrayList()); + mYPositionsContext.add(new ArrayList()); mDeltaXContext.add(new ArrayList()); mDeltaYContext.add(new ArrayList()); mRotationContext.add(new ArrayList()); @@ -86,13 +93,21 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rota mDeltaContext.add(mCurrentDelta); if (positionX != null) { - location.x = PropHelper.fromPercentageToFloat(positionX, mWidth, 0, mScale); + ArrayList list = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); + mXPositionsContext.add(list); + location.x = PropHelper.fromPercentageToFloat(list.get(0), mWidth, 0, mScale); mCurrentDelta.x = 0; + } else { + mXPositionsContext.add(new ArrayList()); } if (positionY != null) { - location.y = PropHelper.fromPercentageToFloat(positionY, mHeight, 0, mScale); + ArrayList list = new ArrayList<>(Arrays.asList(positionY.trim().split("\\s+"))); + mYPositionsContext.add(list); + location.y = PropHelper.fromPercentageToFloat(list.get(0), mHeight, 0, mScale); mCurrentDelta.y = 0; + } else { + mYPositionsContext.add(new ArrayList()); } mCurrentDelta = clonePointF(mCurrentDelta); @@ -116,6 +131,8 @@ public void popContext() { float r = mGlyphRotationContext.get(mContextLength - 1); mFontContext.remove(mContextLength - 1); mLocationContext.remove(mContextLength - 1); + mXPositionsContext.remove(mContextLength - 1); + mYPositionsContext.remove(mContextLength - 1); mDeltaContext.remove(mContextLength - 1); mDeltaXContext.remove(mContextLength - 1); mDeltaYContext.remove(mContextLength - 1); @@ -141,6 +158,15 @@ public void popContext() { } public PointF getNextGlyphPoint(float offset, float glyphWidth) { + if (hasNextString(mXPositionsContext)) { + mCurrentLocation.x = PropHelper.fromPercentageToFloat(getNextString(mXPositionsContext), mWidth, 0, mScale); + mCurrentDelta.x = 0; + } + if (hasNextString(mYPositionsContext)) { + mCurrentLocation.y = PropHelper.fromPercentageToFloat(getNextString(mYPositionsContext), mHeight, 0, mScale); + mCurrentDelta.y = 0; + } + mXContext.set(mXContext.size() - 1, mCurrentLocation.x + offset + glyphWidth); mYContext.set(mYContext.size() - 1, mCurrentLocation.y); @@ -187,6 +213,20 @@ private float getNextFloat(ArrayList> context) { return getNextFloat(context, 0); } + private boolean hasNextString(ArrayList> context) { + int index = mContextLength - 1; + + for (; index >= 0; index--) { + ArrayList delta = context.get(index); + + if (delta.size() != 0) { + return true; + } + } + + return false; + } + private boolean hasNextFloat(ArrayList> context) { int index = mContextLength - 1; @@ -221,6 +261,27 @@ private float getNextFloat(ArrayList> context, float value) { return value; } + private String getNextString(ArrayList> context) { + String value = ""; + boolean valueSet = false; + int index = mContextLength - 1; + + for (; index >= 0; index--) { + ArrayList delta = context.get(index); + + if (delta.size() != 0) { + if (!valueSet) { + value = delta.get(0); + valueSet = true; + } + + delta.remove(0); + } + } + + return value; + } + public ReadableMap getGlyphFont() { String fontFamily = null; float fontSize = DEFAULT_FONT_SIZE; From dc61e9772518ca0536bae0f3ba1f5e216ea83db0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 16 Jul 2017 06:34:31 +0300 Subject: [PATCH 036/198] Fix layout of ligaturized text sequences (e.g. "fi") Optimize kerning calculation. Remove workaround for too early cut-off. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 52f25a3f6..446b2ac1a 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -139,29 +139,25 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { PointF glyphDelta; float glyphRotation; String previous = ""; + float previousWidth = 0; char[] chars = line.toCharArray(); - float[] widths = new float[length]; float glyphPosition = startOffset + textAnchorShift; double kerningValue = font.getDouble(PROP_KERNING) * mScale; boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); - paint.getTextWidths(line, widths); - for (int index = 0; index < length; index++) { - width = widths[index] * renderMethodScaling; current = String.valueOf(chars[index]); + width = paint.measureText(current) * renderMethodScaling; glyph = new Path(); if (isKerningValueSet) { glyphPosition += kerningValue; } else { float bothWidth = paint.measureText(previous + current); - float previousWidth = paint.measureText(previous); - float currentWidth = paint.measureText(current); - float kernedWidth = bothWidth - previousWidth; - float kerning = kernedWidth - currentWidth; + float kerning = bothWidth - previousWidth - width; glyphPosition += kerning; + previousWidth = width; previous = current; } @@ -177,14 +173,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { float midpoint = start + halfway; if (midpoint > distance ) { - if (start <= distance) { - // Seems to cut off too early, see e.g. toap3, this shows the last "p" - // Tangent will be from start position instead of midpoint - midpoint = start; - halfway = 0; - } else { - break; - } + break; } else if (midpoint < 0) { continue; } From 9e47bac463bcd2e18ad86896fe194ee09978d52f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 16 Jul 2017 08:09:35 +0300 Subject: [PATCH 037/198] Refactor Cleanup, organize, simplify, optimize and improve readability :) --- .../java/com/horcrux/svg/TSpanShadowNode.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 446b2ac1a..d51d86389 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -116,10 +116,8 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { float startOffset = 0; float renderMethodScaling = 1; float textMeasure = paint.measureText(line); - float textAnchorShift = getTextAnchorShift(textMeasure); PathMeasure pm = null; - if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); @@ -141,15 +139,15 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { String previous = ""; float previousWidth = 0; char[] chars = line.toCharArray(); - float glyphPosition = startOffset + textAnchorShift; - double kerningValue = font.getDouble(PROP_KERNING) * mScale; boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); + float glyphPosition = startOffset + getTextAnchorShift(textMeasure); for (int index = 0; index < length; index++) { + glyph = new Path(); current = String.valueOf(chars[index]); + paint.getTextPath(current, 0, 1, 0, 0, glyph); width = paint.measureText(current) * renderMethodScaling; - glyph = new Path(); if (isKerningValueSet) { glyphPosition += kerningValue; @@ -162,15 +160,14 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { } glyphPoint = getGlyphPointFromContext(glyphPosition, width); - glyphDelta = getGlyphDeltaFromContext(); glyphRotation = getNextGlyphRotationFromContext(); + glyphDelta = getGlyphDeltaFromContext(); glyphPosition += width; matrix = new Matrix(); if (textPath != null) { float halfway = width / 2; - float start = glyphPoint.x + glyphDelta.x; - float midpoint = start + halfway; + float midpoint = glyphPoint.x + glyphDelta.x + halfway; if (midpoint > distance ) { break; @@ -191,8 +188,6 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { } matrix.preRotate(glyphRotation); - - paint.getTextPath(current, 0, 1, 0, 0, glyph); glyph.transform(matrix); path.addPath(glyph); } From 6bfee9395c53f48ecf62c8590ebbeed69d6a91b5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 17 Jul 2017 12:39:26 +0300 Subject: [PATCH 038/198] Fix kerning calculation for render method stretch --- android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index d51d86389..46f3049b7 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -152,7 +152,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { if (isKerningValueSet) { glyphPosition += kerningValue; } else { - float bothWidth = paint.measureText(previous + current); + float bothWidth = paint.measureText(previous + current) * renderMethodScaling; float kerning = bothWidth - previousWidth - width; glyphPosition += kerning; previousWidth = width; From c89fdd31c23ff419867fb749a9da41fadadc10f7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 17 Jul 2017 18:14:47 +0300 Subject: [PATCH 039/198] =?UTF-8?q?Fix=20propagation=20of=20the=20?= =?UTF-8?q?=E2=80=98rotate=E2=80=99=20attribute.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://www.w3.org/TR/SVG/text.html#TSpanElementRotateAttribute https://www.w3.org/TR/SVG/text.html#ExampleTSpan03 Example tspan04, tspan05. --- .../java/com/horcrux/svg/GlyphContext.java | 146 ++++++++---------- 1 file changed, 68 insertions(+), 78 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 859b49b49..a156382b9 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -18,6 +18,8 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -32,6 +34,7 @@ public class GlyphContext { private ArrayList> mDeltaXContext; private ArrayList> mDeltaYContext; private ArrayList> mRotationContext; + private ArrayList mRotationIndexContext; private ArrayList mGlyphRotationContext; private ArrayList mXContext; private ArrayList mYContext; @@ -42,6 +45,7 @@ public class GlyphContext { private float mWidth; private float mHeight; private int mContextLength = 0; + private int mRotationIndex = -1; private static final float DEFAULT_FONT_SIZE = 12f; private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_LETTER_SPACING = 0f; @@ -60,6 +64,7 @@ public class GlyphContext { mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); mRotationContext = new ArrayList<>(); + mRotationIndexContext = new ArrayList<>(); mGlyphRotationContext = new ArrayList<>(); mXContext = new ArrayList<>(); mYContext = new ArrayList<>(); @@ -78,8 +83,7 @@ public void pushContext(@Nullable ReadableMap font) { mYPositionsContext.add(new ArrayList()); mDeltaXContext.add(new ArrayList()); mDeltaYContext.add(new ArrayList()); - mRotationContext.add(new ArrayList()); - mGlyphRotationContext.add(mRotation); + mRotationIndexContext.add(mRotationIndex); mXContext.add(location.x); mYContext.add(location.y); @@ -116,11 +120,19 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rota mFontContext.add(font); mDeltaXContext.add(getFloatArrayListFromReadableArray(deltaX)); mDeltaYContext.add(getFloatArrayListFromReadableArray(deltaY)); - mRotationContext.add(getFloatArrayListFromReadableArray(rotate)); - mGlyphRotationContext.add(mRotation); mXContext.add(location.x); mYContext.add(location.y); + ArrayList rotations = getFloatArrayListFromReadableArray(rotate); + if (rotations.size() != 0) { + mRotationIndex = mRotationContext.size(); + mRotationContext.add(rotations); + mRotation = rotations.get(0); + mGlyphRotationContext.add(mRotation); + } + + mRotationIndexContext.add(mRotationIndex); + mCurrentLocation = clonePointF(location); mContextLength++; } @@ -128,7 +140,6 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rota public void popContext() { float x = mXContext.get(mContextLength - 1); float y = mYContext.get(mContextLength - 1); - float r = mGlyphRotationContext.get(mContextLength - 1); mFontContext.remove(mContextLength - 1); mLocationContext.remove(mContextLength - 1); mXPositionsContext.remove(mContextLength - 1); @@ -136,33 +147,37 @@ public void popContext() { mDeltaContext.remove(mContextLength - 1); mDeltaXContext.remove(mContextLength - 1); mDeltaYContext.remove(mContextLength - 1); - mRotationContext.remove(mContextLength - 1); - mGlyphRotationContext.remove(mContextLength - 1); + mRotationIndexContext.remove(mContextLength - 1); mXContext.remove(mContextLength - 1); mYContext.remove(mContextLength - 1); mContextLength--; + if (mRotationIndex == mContextLength) { + mRotationContext.remove(mRotationIndex); + mGlyphRotationContext.remove(mRotationIndex); + } + if (mContextLength != 0) { mXContext.set(mContextLength - 1, x); mYContext.set(mContextLength - 1, y); - mGlyphRotationContext.set(mContextLength - 1, r); PointF lastLocation = mLocationContext.get(mContextLength - 1); PointF lastDelta = mDeltaContext.get(mContextLength - 1); mCurrentLocation = clonePointF(lastLocation); mCurrentDelta = clonePointF(lastDelta); mCurrentLocation.x = lastLocation.x = x; mCurrentLocation.y = lastLocation.y = y; - mRotation = r; + mRotationIndex = mRotationIndexContext.get(mContextLength - 1); + mRotation = mRotationIndex > -1 ? mGlyphRotationContext.get(mRotationIndex) : 0f; } } public PointF getNextGlyphPoint(float offset, float glyphWidth) { - if (hasNextString(mXPositionsContext)) { + if (hasNext(mXPositionsContext)) { mCurrentLocation.x = PropHelper.fromPercentageToFloat(getNextString(mXPositionsContext), mWidth, 0, mScale); mCurrentDelta.x = 0; } - if (hasNextString(mYPositionsContext)) { + if (hasNext(mYPositionsContext)) { mCurrentLocation.y = PropHelper.fromPercentageToFloat(getNextString(mYPositionsContext), mHeight, 0, mScale); mCurrentDelta.y = 0; } @@ -174,52 +189,42 @@ public PointF getNextGlyphPoint(float offset, float glyphWidth) { } public PointF getNextGlyphDelta() { - float dx = mScale * getNextFloat(mDeltaXContext); - float dy = mScale * getNextFloat(mDeltaYContext); + float dx = 0f; + float dy = 0f; - if (mContextLength > 0) { - for (PointF point: mDeltaContext) { - point.x += dx; - point.y += dy; + if (hasNext(mDeltaXContext) || hasNext(mDeltaYContext)) { + dx = mScale * getNextFloat(mDeltaXContext); + dy = mScale * getNextFloat(mDeltaYContext); + + if (mContextLength > 0) { + for (PointF point : mDeltaContext) { + point.x += dx; + point.y += dy; + } } + } + if (mContextLength > 0) { return mDeltaContext.get(mContextLength - 1); + } else { + return new PointF(dx, dy); } - - return new PointF(dx, dy); } public float getNextGlyphRotation() { - if (hasNextFloat(mRotationContext)) { - float r = getNextFloat(mRotationContext); - - if (mContextLength > 0) { - for (int i = 0; i < mContextLength; i++) { - mGlyphRotationContext.set(i, r); - } - - return mGlyphRotationContext.get(mContextLength - 1); - } - - return r; - } else if (mContextLength > 0) { - return mGlyphRotationContext.get(mContextLength - 1); + if (mRotationIndex > -1 && hasNext(mRotationContext, mRotationIndex)) { + mRotation = getNextFloat(mRotationContext, mRotationIndex); + mGlyphRotationContext.set(mRotationIndex, mRotation); } - return 0; - } - - private float getNextFloat(ArrayList> context) { - return getNextFloat(context, 0); + return mRotation; } - private boolean hasNextString(ArrayList> context) { + private boolean hasNext(List> context) { int index = mContextLength - 1; for (; index >= 0; index--) { - ArrayList delta = context.get(index); - - if (delta.size() != 0) { + if (hasNext(context, index)) { return true; } } @@ -227,59 +232,44 @@ private boolean hasNextString(ArrayList> context) { return false; } - private boolean hasNextFloat(ArrayList> context) { - int index = mContextLength - 1; + private boolean hasNext(List> context, int index) { + return context.get(index).size() != 0; + } - for (; index >= 0; index--) { - ArrayList delta = context.get(index); + private float getNextFloat(ArrayList> context) { + return getNext(context, 0f); + } - if (delta.size() != 0) { - return true; - } - } + private float getNextFloat(ArrayList> context, int index) { + return getNext(context, index, 0f); + } - return false; + private String getNextString(ArrayList> context) { + return getNext(context, ""); } - private float getNextFloat(ArrayList> context, float value) { - boolean valueSet = false; + private T getNext(List> context, T lacuna) { int index = mContextLength - 1; for (; index >= 0; index--) { - ArrayList delta = context.get(index); - - if (delta.size() != 0) { - if (!valueSet) { - value = delta.get(0); - valueSet = true; - } + List list = context.get(index); - delta.remove(0); + if (list.size() != 0) { + return list.remove(0); } } - return value; + return lacuna; } - private String getNextString(ArrayList> context) { - String value = ""; - boolean valueSet = false; - int index = mContextLength - 1; - - for (; index >= 0; index--) { - ArrayList delta = context.get(index); + private T getNext(List> context, int index, T lacuna) { + List list = context.get(index); - if (delta.size() != 0) { - if (!valueSet) { - value = delta.get(0); - valueSet = true; - } - - delta.remove(0); - } + if (list.size() != 0) { + return list.remove(0); } - return value; + return lacuna; } public ReadableMap getGlyphFont() { From 99406fc0c5da80456cf0c3472a41ff5d87063865 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 18 Jul 2017 01:25:38 +0300 Subject: [PATCH 040/198] =?UTF-8?q?Fix=20propagation=20of=20the=20?= =?UTF-8?q?=E2=80=98dx=E2=80=99=20and=20=E2=80=98dy=E2=80=99=20attribute.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve readability of GlyphContext. --- .../java/com/horcrux/svg/GlyphContext.java | 304 +++++++++++------- 1 file changed, 183 insertions(+), 121 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index a156382b9..9822bb868 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -26,159 +26,203 @@ public class GlyphContext { - private ArrayList mFontContext; - private ArrayList mLocationContext; - private ArrayList mDeltaContext; private ArrayList> mXPositionsContext; private ArrayList> mYPositionsContext; - private ArrayList> mDeltaXContext; - private ArrayList> mDeltaYContext; - private ArrayList> mRotationContext; + private ArrayList> mRotationsContext; + private ArrayList> mDeltaXsContext; + private ArrayList> mDeltaYsContext; private ArrayList mRotationIndexContext; - private ArrayList mGlyphRotationContext; + private ArrayList mDeltaXIndexContext; + private ArrayList mDeltaYIndexContext; + private ArrayList mFontContext; + private ArrayList mLocationContext; + private ArrayList mRotationContext; + private ArrayList mDeltaXContext; + private ArrayList mDeltaYContext; private ArrayList mXContext; private ArrayList mYContext; + private @Nonnull PointF mCurrentLocation; private @Nonnull PointF mCurrentDelta; - private float mRotation = 0; + private float mScale; private float mWidth; private float mHeight; - private int mContextLength = 0; + private float mRotation; + + private int mContextLength; + private int mDeltaXIndex = -1; + private int mDeltaYIndex = -1; private int mRotationIndex = -1; - private static final float DEFAULT_FONT_SIZE = 12f; + private static final float DEFAULT_KERNING = 0f; + private static final float DEFAULT_FONT_SIZE = 12f; private static final float DEFAULT_LETTER_SPACING = 0f; GlyphContext(float scale, float width, float height) { - mScale = scale; - mWidth = width; - mHeight = height; - mCurrentLocation = new PointF(); - mCurrentDelta = new PointF(); - mFontContext = new ArrayList<>(); - mLocationContext = new ArrayList<>(); - mDeltaContext = new ArrayList<>(); + mRotationIndexContext = new ArrayList<>(); + mDeltaXIndexContext = new ArrayList<>(); + mDeltaYIndexContext = new ArrayList<>(); mXPositionsContext = new ArrayList<>(); mYPositionsContext = new ArrayList<>(); + mRotationsContext = new ArrayList<>(); + mRotationContext = new ArrayList<>(); + mLocationContext = new ArrayList<>(); + mDeltaXsContext = new ArrayList<>(); + mDeltaYsContext = new ArrayList<>(); mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); - mRotationContext = new ArrayList<>(); - mRotationIndexContext = new ArrayList<>(); - mGlyphRotationContext = new ArrayList<>(); + mFontContext = new ArrayList<>(); mXContext = new ArrayList<>(); mYContext = new ArrayList<>(); - } - public void pushContext(@Nullable ReadableMap font) { - PointF location = mCurrentLocation; + mCurrentLocation = new PointF(); + mCurrentDelta = new PointF(); - mDeltaContext.add(mCurrentDelta); + mHeight = height; + mWidth = width; + mScale = scale; + } - mCurrentDelta = clonePointF(mCurrentDelta); + public void pushContext(@Nullable ReadableMap font) { + mCurrentLocation = clonePointF(mCurrentLocation); - mLocationContext.add(location); - mFontContext.add(font); mXPositionsContext.add(new ArrayList()); mYPositionsContext.add(new ArrayList()); - mDeltaXContext.add(new ArrayList()); - mDeltaYContext.add(new ArrayList()); mRotationIndexContext.add(mRotationIndex); - mXContext.add(location.x); - mYContext.add(location.y); + mLocationContext.add(mCurrentLocation); + mDeltaXIndexContext.add(mDeltaXIndex); + mDeltaYIndexContext.add(mDeltaYIndex); + mXContext.add(mCurrentLocation.x); + mYContext.add(mCurrentLocation.y); + mFontContext.add(font); - mCurrentLocation = clonePointF(location); mContextLength++; } public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { - PointF location = mCurrentLocation; - - mDeltaContext.add(mCurrentDelta); + mCurrentLocation = clonePointF(mCurrentLocation); if (positionX != null) { ArrayList list = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); + mCurrentLocation.x = PropHelper.fromPercentageToFloat(list.get(0), mWidth, 0, mScale); mXPositionsContext.add(list); - location.x = PropHelper.fromPercentageToFloat(list.get(0), mWidth, 0, mScale); - mCurrentDelta.x = 0; } else { mXPositionsContext.add(new ArrayList()); } if (positionY != null) { ArrayList list = new ArrayList<>(Arrays.asList(positionY.trim().split("\\s+"))); + mCurrentLocation.y = PropHelper.fromPercentageToFloat(list.get(0), mHeight, 0, mScale); mYPositionsContext.add(list); - location.y = PropHelper.fromPercentageToFloat(list.get(0), mHeight, 0, mScale); - mCurrentDelta.y = 0; } else { mYPositionsContext.add(new ArrayList()); } - mCurrentDelta = clonePointF(mCurrentDelta); - - mLocationContext.add(location); - mFontContext.add(font); - mDeltaXContext.add(getFloatArrayListFromReadableArray(deltaX)); - mDeltaYContext.add(getFloatArrayListFromReadableArray(deltaY)); - mXContext.add(location.x); - mYContext.add(location.y); - ArrayList rotations = getFloatArrayListFromReadableArray(rotate); if (rotations.size() != 0) { - mRotationIndex = mRotationContext.size(); - mRotationContext.add(rotations); mRotation = rotations.get(0); - mGlyphRotationContext.add(mRotation); + mRotationContext.add(mRotation); + mRotationsContext.add(rotations); + mRotationIndex = mRotationsContext.size(); + } + + ArrayList deltaXs = getFloatArrayListFromReadableArray(deltaX); + if (deltaXs.size() != 0) { + mDeltaXIndex = mDeltaXsContext.size(); + mDeltaXContext.add(mCurrentDelta.x); + mDeltaXsContext.add(deltaXs); + } + + ArrayList deltaYs = getFloatArrayListFromReadableArray(deltaY); + if (deltaYs.size() != 0) { + mDeltaYIndex = mDeltaYsContext.size(); + mDeltaYContext.add(mCurrentDelta.y); + mDeltaYsContext.add(deltaYs); } mRotationIndexContext.add(mRotationIndex); + mLocationContext.add(mCurrentLocation); + mDeltaXIndexContext.add(mDeltaXIndex); + mDeltaYIndexContext.add(mDeltaYIndex); + mXContext.add(mCurrentLocation.x); + mYContext.add(mCurrentLocation.y); + mFontContext.add(font); - mCurrentLocation = clonePointF(location); mContextLength++; } public void popContext() { + float dx = mDeltaXIndex > -1 ? mDeltaXContext.get(mDeltaXIndex) : 0f; + float dy = mDeltaYIndex > -1 ? mDeltaYContext.get(mDeltaYIndex) : 0f; + float x = mXContext.get(mContextLength - 1); float y = mYContext.get(mContextLength - 1); - mFontContext.remove(mContextLength - 1); - mLocationContext.remove(mContextLength - 1); + + mRotationIndexContext.remove(mContextLength - 1); + mDeltaXIndexContext.remove(mContextLength - 1); + mDeltaYIndexContext.remove(mContextLength - 1); mXPositionsContext.remove(mContextLength - 1); mYPositionsContext.remove(mContextLength - 1); - mDeltaContext.remove(mContextLength - 1); - mDeltaXContext.remove(mContextLength - 1); - mDeltaYContext.remove(mContextLength - 1); - mRotationIndexContext.remove(mContextLength - 1); + mLocationContext.remove(mContextLength - 1); + mFontContext.remove(mContextLength - 1); mXContext.remove(mContextLength - 1); mYContext.remove(mContextLength - 1); mContextLength--; if (mRotationIndex == mContextLength) { + mRotationsContext.remove(mRotationIndex); mRotationContext.remove(mRotationIndex); - mGlyphRotationContext.remove(mRotationIndex); + } + + if (mDeltaXIndex == mContextLength) { + mDeltaXsContext.remove(mDeltaXIndex); + mDeltaXContext.remove(mDeltaXIndex); + } + + if (mDeltaYIndex == mContextLength) { + mDeltaYsContext.remove(mDeltaYIndex); + mDeltaYContext.remove(mDeltaYIndex); } if (mContextLength != 0) { + mRotationIndex = mRotationIndexContext.get(mContextLength - 1); + mCurrentLocation = mLocationContext.get(mContextLength - 1); + mDeltaXIndex = mDeltaXIndexContext.get(mContextLength - 1); + mDeltaYIndex = mDeltaYIndexContext.get(mContextLength - 1); + mXContext.set(mContextLength - 1, x); mYContext.set(mContextLength - 1, y); - PointF lastLocation = mLocationContext.get(mContextLength - 1); - PointF lastDelta = mDeltaContext.get(mContextLength - 1); - mCurrentLocation = clonePointF(lastLocation); - mCurrentDelta = clonePointF(lastDelta); - mCurrentLocation.x = lastLocation.x = x; - mCurrentLocation.y = lastLocation.y = y; - mRotationIndex = mRotationIndexContext.get(mContextLength - 1); - mRotation = mRotationIndex > -1 ? mGlyphRotationContext.get(mRotationIndex) : 0f; + + if (mDeltaXIndex > -1) { + mDeltaXContext.set(mDeltaXIndex, dx); + } + if (mDeltaYIndex > -1) { + mDeltaYContext.set(mDeltaYIndex, dy); + } + + mCurrentLocation.x = x; + mCurrentLocation.y = y; + mCurrentDelta.x = dx; + mCurrentDelta.y = dy; + + mRotation = mRotationIndex > -1 ? mRotationContext.get(mRotationIndex) : 0f; + } else { + mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; + mDeltaXIndex = mDeltaYIndex = mRotationIndex = -1; } } public PointF getNextGlyphPoint(float offset, float glyphWidth) { if (hasNext(mXPositionsContext)) { mCurrentLocation.x = PropHelper.fromPercentageToFloat(getNextString(mXPositionsContext), mWidth, 0, mScale); + resetDelta(mDeltaXContext); mCurrentDelta.x = 0; } if (hasNext(mYPositionsContext)) { mCurrentLocation.y = PropHelper.fromPercentageToFloat(getNextString(mYPositionsContext), mHeight, 0, mScale); + resetDelta(mDeltaYContext); mCurrentDelta.y = 0; } @@ -188,33 +232,40 @@ public PointF getNextGlyphPoint(float offset, float glyphWidth) { return new PointF(mCurrentLocation.x + offset, mCurrentLocation.y); } - public PointF getNextGlyphDelta() { - float dx = 0f; - float dy = 0f; - - if (hasNext(mDeltaXContext) || hasNext(mDeltaYContext)) { - dx = mScale * getNextFloat(mDeltaXContext); - dy = mScale * getNextFloat(mDeltaYContext); - - if (mContextLength > 0) { - for (PointF point : mDeltaContext) { - point.x += dx; - point.y += dy; - } - } + private static void resetDelta(ArrayList context) { + for (int i = context.size() - 1; i >= 0; i--) { + context.set(i, 0f); } + } - if (mContextLength > 0) { - return mDeltaContext.get(mContextLength - 1); - } else { - return new PointF(dx, dy); + public PointF getNextGlyphDelta() { + if (mDeltaXIndex > -1) { + mCurrentDelta.x = getNextDelta(mDeltaXsContext, mDeltaXContext); } + if (mDeltaYIndex > -1) { + mCurrentDelta.y = getNextDelta(mDeltaYsContext, mDeltaYContext); + } + + return mCurrentDelta; } public float getNextGlyphRotation() { - if (mRotationIndex > -1 && hasNext(mRotationContext, mRotationIndex)) { - mRotation = getNextFloat(mRotationContext, mRotationIndex); - mGlyphRotationContext.set(mRotationIndex, mRotation); + if (mRotationIndex > -1) { + int index = mRotationsContext.size() - 1; + int top = index; + + for (; index >= 0; index--) { + List rotations = mRotationsContext.get(index); + + if (rotations.size() != 0) { + float val = rotations.remove(0); + mRotationContext.set(index, val); + + if (index == top) { + mRotation = val; + } + } + } } return mRotation; @@ -236,50 +287,61 @@ private boolean hasNext(List> context, int index) { return context.get(index).size() != 0; } - private float getNextFloat(ArrayList> context) { - return getNext(context, 0f); - } + private float getNextDelta(List> lists, List contexts) { + int index = lists.size() - 1; + int top = index; - private float getNextFloat(ArrayList> context, int index) { - return getNext(context, index, 0f); - } - - private String getNextString(ArrayList> context) { - return getNext(context, ""); - } - - private T getNext(List> context, T lacuna) { - int index = mContextLength - 1; + float delta = index > -1 ? contexts.get(index) : 0; for (; index >= 0; index--) { - List list = context.get(index); + List deltas = lists.get(index); - if (list.size() != 0) { - return list.remove(0); + if (deltas.size() != 0) { + float val = deltas.remove(0); + + if (top == index) { + float current = contexts.get(index); + float acc = current + val * mScale; + contexts.set(index, acc); + delta = acc; + } } } - return lacuna; + return delta; } - private T getNext(List> context, int index, T lacuna) { - List list = context.get(index); + public String getNextString(ArrayList> lists) { + int index = lists.size() - 1; + boolean valueSet = false; + String value = ""; + + for (; index >= 0; index--) { + List list = lists.get(index); + + if (list.size() != 0) { + String val = list.remove(0); - if (list.size() != 0) { - return list.remove(0); + if (!valueSet) { + valueSet = true; + value = val; + } + } } - return lacuna; + return value; } public ReadableMap getGlyphFont() { - String fontFamily = null; + float letterSpacing = DEFAULT_LETTER_SPACING; float fontSize = DEFAULT_FONT_SIZE; float kerning = DEFAULT_KERNING; - float letterSpacing = DEFAULT_LETTER_SPACING; + + boolean letterSpacingSet = false; boolean fontSizeSet = false; boolean kerningSet = false; - boolean letterSpacingSet = false; + + String fontFamily = null; String fontWeight = null; String fontStyle = null; @@ -293,7 +355,7 @@ public ReadableMap getGlyphFont() { } if (!fontSizeSet && font.hasKey("fontSize")) { - fontSize = (float)font.getDouble("fontSize"); + fontSize = (float) font.getDouble("fontSize"); fontSizeSet = true; } @@ -322,21 +384,21 @@ public ReadableMap getGlyphFont() { } WritableMap map = Arguments.createMap(); + + map.putBoolean("isKerningValueSet", kerningSet); + map.putDouble("letterSpacing", letterSpacing); map.putString("fontFamily", fontFamily); - map.putDouble("fontSize", fontSize); map.putString("fontWeight", fontWeight); map.putString("fontStyle", fontStyle); + map.putDouble("fontSize", fontSize); map.putDouble("kerning", kerning); - map.putDouble("letterSpacing", letterSpacing); - map.putBoolean("isKerningValueSet", kerningSet); return map; } private ArrayList getFloatArrayListFromReadableArray(ReadableArray readableArray) { + float fontSize = (float) getGlyphFont().getDouble("fontSize"); ArrayList arrayList = new ArrayList<>(); - ReadableMap font = getGlyphFont(); - float fontSize = (float)font.getDouble("fontSize"); if (readableArray != null) { for (int i = 0; i < readableArray.size(); i++) { @@ -347,7 +409,7 @@ private ArrayList getFloatArrayListFromReadableArray(ReadableArray readab break; case Number: - arrayList.add((float)readableArray.getDouble(i)); + arrayList.add((float) readableArray.getDouble(i)); break; } } From ae48626815e596accb85f76a2a8560f3e17f3677 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 18 Jul 2017 01:46:50 +0300 Subject: [PATCH 041/198] Fix order of mRotationIndex calculation --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 9822bb868..db5e47ef9 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -121,10 +121,10 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rota ArrayList rotations = getFloatArrayListFromReadableArray(rotate); if (rotations.size() != 0) { + mRotationIndex = mRotationsContext.size(); + mRotationsContext.add(rotations); mRotation = rotations.get(0); mRotationContext.add(mRotation); - mRotationsContext.add(rotations); - mRotationIndex = mRotationsContext.size(); } ArrayList deltaXs = getFloatArrayListFromReadableArray(deltaX); From b456fdb600759e7f769cee99e41e0cbf4c9dbc9d Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 18 Jul 2017 02:53:33 +0300 Subject: [PATCH 042/198] Fix warnings, cleanup. --- .../java/com/horcrux/svg/GlyphContext.java | 57 +++++++++---------- .../java/com/horcrux/svg/GroupShadowNode.java | 25 ++++---- .../java/com/horcrux/svg/TSpanShadowNode.java | 19 ++++--- .../java/com/horcrux/svg/TextShadowNode.java | 29 ++++------ 4 files changed, 61 insertions(+), 69 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index db5e47ef9..54a9d44fb 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -24,7 +24,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class GlyphContext { +class GlyphContext { private ArrayList> mXPositionsContext; private ArrayList> mYPositionsContext; @@ -84,7 +84,7 @@ public class GlyphContext { mScale = scale; } - public void pushContext(@Nullable ReadableMap font) { + void pushContext(@Nullable ReadableMap font) { mCurrentLocation = clonePointF(mCurrentLocation); mXPositionsContext.add(new ArrayList()); @@ -100,7 +100,7 @@ public void pushContext(@Nullable ReadableMap font) { mContextLength++; } - public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { + void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { mCurrentLocation = clonePointF(mCurrentLocation); if (positionX != null) { @@ -152,24 +152,24 @@ public void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rota mContextLength++; } - public void popContext() { + void popContext() { + mContextLength--; + float dx = mDeltaXIndex > -1 ? mDeltaXContext.get(mDeltaXIndex) : 0f; float dy = mDeltaYIndex > -1 ? mDeltaYContext.get(mDeltaYIndex) : 0f; - float x = mXContext.get(mContextLength - 1); - float y = mYContext.get(mContextLength - 1); + float x = mXContext.get(mContextLength); + float y = mYContext.get(mContextLength); - mRotationIndexContext.remove(mContextLength - 1); - mDeltaXIndexContext.remove(mContextLength - 1); - mDeltaYIndexContext.remove(mContextLength - 1); - mXPositionsContext.remove(mContextLength - 1); - mYPositionsContext.remove(mContextLength - 1); - mLocationContext.remove(mContextLength - 1); - mFontContext.remove(mContextLength - 1); - mXContext.remove(mContextLength - 1); - mYContext.remove(mContextLength - 1); - - mContextLength--; + mRotationIndexContext.remove(mContextLength); + mDeltaXIndexContext.remove(mContextLength); + mDeltaYIndexContext.remove(mContextLength); + mXPositionsContext.remove(mContextLength); + mYPositionsContext.remove(mContextLength); + mLocationContext.remove(mContextLength); + mFontContext.remove(mContextLength); + mXContext.remove(mContextLength); + mYContext.remove(mContextLength); if (mRotationIndex == mContextLength) { mRotationsContext.remove(mRotationIndex); @@ -211,10 +211,11 @@ public void popContext() { } else { mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; mDeltaXIndex = mDeltaYIndex = mRotationIndex = -1; + mCurrentLocation.x = mCurrentLocation.y = 0; } } - public PointF getNextGlyphPoint(float offset, float glyphWidth) { + PointF getNextGlyphPoint(float offset, float glyphWidth) { if (hasNext(mXPositionsContext)) { mCurrentLocation.x = PropHelper.fromPercentageToFloat(getNextString(mXPositionsContext), mWidth, 0, mScale); resetDelta(mDeltaXContext); @@ -232,13 +233,13 @@ public PointF getNextGlyphPoint(float offset, float glyphWidth) { return new PointF(mCurrentLocation.x + offset, mCurrentLocation.y); } - private static void resetDelta(ArrayList context) { + private void resetDelta(ArrayList context) { for (int i = context.size() - 1; i >= 0; i--) { context.set(i, 0f); } } - public PointF getNextGlyphDelta() { + PointF getNextGlyphDelta() { if (mDeltaXIndex > -1) { mCurrentDelta.x = getNextDelta(mDeltaXsContext, mDeltaXContext); } @@ -249,7 +250,7 @@ public PointF getNextGlyphDelta() { return mCurrentDelta; } - public float getNextGlyphRotation() { + float getNextGlyphRotation() { if (mRotationIndex > -1) { int index = mRotationsContext.size() - 1; int top = index; @@ -311,7 +312,7 @@ private float getNextDelta(List> lists, List contex return delta; } - public String getNextString(ArrayList> lists) { + private String getNextString(ArrayList> lists) { int index = lists.size() - 1; boolean valueSet = false; String value = ""; @@ -332,7 +333,7 @@ public String getNextString(ArrayList> lists) { return value; } - public ReadableMap getGlyphFont() { + ReadableMap getGlyphFont() { float letterSpacing = DEFAULT_LETTER_SPACING; float fontSize = DEFAULT_FONT_SIZE; float kerning = DEFAULT_KERNING; @@ -345,9 +346,8 @@ public ReadableMap getGlyphFont() { String fontWeight = null; String fontStyle = null; - int index = mContextLength - 1; - - for (; index >= 0; index--) { + // TODO: add support for other length units + for (int index = mContextLength - 1; index >= 0; index--) { ReadableMap font = mFontContext.get(index); if (fontFamily == null && font.hasKey("fontFamily")) { @@ -359,13 +359,11 @@ public ReadableMap getGlyphFont() { fontSizeSet = true; } - // TODO: add support for other length units if (!kerningSet && font.hasKey("kerning")) { kerning = Float.valueOf(font.getString("kerning")); kerningSet = true; } - // TODO: add support for other length units if (!letterSpacingSet && font.hasKey("letterSpacing")) { letterSpacing = Float.valueOf(font.getString("letterSpacing")); letterSpacingSet = true; @@ -374,6 +372,7 @@ public ReadableMap getGlyphFont() { if (fontWeight == null && font.hasKey("fontWeight")) { fontWeight = font.getString("fontWeight"); } + if (fontStyle == null && font.hasKey("fontStyle")) { fontStyle = font.getString("fontStyle"); } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 65de56fc8..332ce5e71 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -20,14 +20,13 @@ import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; - import javax.annotation.Nullable; /** * Shadow node for virtual Group view */ -public class GroupShadowNode extends RenderableShadowNode { - protected @Nullable ReadableMap mFont; +class GroupShadowNode extends RenderableShadowNode { + @Nullable ReadableMap mFont; private GlyphContext mGlyphContext; private GroupShadowNode mTextRoot; @@ -38,7 +37,7 @@ public void setFont(@Nullable ReadableMap font) { markUpdated(); } - protected GroupShadowNode getTextRoot() { + GroupShadowNode getTextRoot() { GroupShadowNode shadowNode = getShadowNode(GroupShadowNode.class); if (shadowNode == null) { return getShadowNode(TextShadowNode.class); @@ -70,11 +69,11 @@ private GroupShadowNode getShadowNode(Class shadowNodeClass) { return mTextRoot; } - protected void setupGlyphContext() { + void setupGlyphContext() { mGlyphContext = new GlyphContext(mScale, getCanvasWidth(), getCanvasHeight()); } - protected GlyphContext getGlyphContext() { + GlyphContext getGlyphContext() { if (mGlyphContext == null) { setupGlyphContext(); } @@ -89,19 +88,19 @@ protected void popGlyphContext() { getTextRoot().getGlyphContext().popContext(); } - protected ReadableMap getFontFromContext() { + ReadableMap getFontFromContext() { return getTextRoot().getGlyphContext().getGlyphFont(); } - protected PointF getGlyphPointFromContext(float offset, float glyphWidth) { + PointF getGlyphPointFromContext(float offset, float glyphWidth) { return getTextRoot().getGlyphContext().getNextGlyphPoint(offset, glyphWidth); } - protected PointF getGlyphDeltaFromContext() { + PointF getGlyphDeltaFromContext() { return getTextRoot().getGlyphContext().getNextGlyphDelta(); } - protected float getNextGlyphRotationFromContext() { + float getNextGlyphRotationFromContext() { return getTextRoot().getGlyphContext().getNextGlyphRotation(); } @@ -113,7 +112,7 @@ public void draw(final Canvas canvas, final Paint paint, final float opacity) { } } - protected void drawGroup(final Canvas canvas, final Paint paint, final float opacity) { + void drawGroup(final Canvas canvas, final Paint paint, final float opacity) { pushGlyphContext(); final SvgViewShadowNode svg = getSvgShadowNode(); final GroupShadowNode self = this; @@ -142,7 +141,7 @@ public boolean run(VirtualNode node) { popGlyphContext(); } - protected void drawPath(Canvas canvas, Paint paint, float opacity) { + void drawPath(Canvas canvas, Paint paint, float opacity) { super.draw(canvas, paint, opacity); } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 46f3049b7..3469fb3d6 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -32,19 +32,19 @@ /** * Shadow node for virtual TSpan view */ -public class TSpanShadowNode extends TextShadowNode { +class TSpanShadowNode extends TextShadowNode { private Path mCache; private @Nullable String mContent; private TextPathShadowNode textPath; - private static final String PROP_FONT_FAMILY = "fontFamily"; + private static final String PROP_KERNING = "kerning"; 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 String PROP_KERNING = "kerning"; - private static final String PROP_IS_KERNING_VALUE_SET = "isKerningValueSet"; + private static final String PROP_FONT_FAMILY = "fontFamily"; private static final String PROP_LETTER_SPACING = "letterSpacing"; + private static final String PROP_IS_KERNING_VALUE_SET = "isKerningValueSet"; @ReactProp(name = "content") public void setContent(@Nullable String content) { @@ -80,7 +80,7 @@ protected Path getPath(Canvas canvas, Paint paint) { setupTextPath(); pushGlyphContext(); - Path path = mCache = getLinePath(canvas, mContent, paint); + Path path = mCache = getLinePath(mContent, paint); popGlyphContext(); path.computeBounds(new RectF(), true); @@ -103,8 +103,8 @@ private float getTextAnchorShift(float width) { return x; } - private Path getLinePath(Canvas canvas, String line, Paint paint) { - ReadableMap font = applyTextPropertiesToPaint(paint, canvas); + private Path getLinePath(String line, Paint paint) { + ReadableMap font = applyTextPropertiesToPaint(paint); int length = line.length(); Path path = new Path(); @@ -175,6 +175,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { continue; } + assert pm != null; pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); matrix.preTranslate(-halfway, glyphDelta.y); @@ -195,7 +196,7 @@ private Path getLinePath(Canvas canvas, String line, Paint paint) { return path; } - private ReadableMap applyTextPropertiesToPaint(Paint paint, Canvas canvas) { + private ReadableMap applyTextPropertiesToPaint(Paint paint) { ReadableMap font = getFontFromContext(); paint.setTextAlign(Paint.Align.LEFT); diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index e0e767fa0..7c69b4c88 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -9,40 +9,33 @@ package com.horcrux.svg; -import javax.annotation.Nullable; - import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.Log; -import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; +import javax.annotation.Nullable; + /** * Shadow node for virtual Text view */ -public class TextShadowNode extends GroupShadowNode { +class TextShadowNode extends GroupShadowNode { - static final int TEXT_ANCHOR_AUTO = 0; - static final int TEXT_ANCHOR_START = 1; + private static final int TEXT_ANCHOR_AUTO = 0; + // static final int TEXT_ANCHOR_START = 1; static final int TEXT_ANCHOR_MIDDLE = 2; static final int TEXT_ANCHOR_END = 3; - static final int TEXT_DECORATION_NONE = 0; + private static final int TEXT_DECORATION_NONE = 0; static final int TEXT_DECORATION_UNDERLINE = 1; - static final int TEXT_DECORATION_OVERLINE = 2; + // static final int TEXT_DECORATION_OVERLINE = 2; static final int TEXT_DECORATION_LINE_THROUGH = 3; - static final int TEXT_DECORATION_BLINK = 4; + // static final int TEXT_DECORATION_BLINK = 4; private int mTextAnchor = TEXT_ANCHOR_AUTO; private int mTextDecoration = TEXT_DECORATION_NONE; @@ -105,7 +98,7 @@ public void draw(Canvas canvas, Paint paint, float opacity) { if (opacity > MIN_OPACITY_FOR_DRAW) { setupGlyphContext(); clip(canvas, paint); - Path path = getGroupPath(canvas, paint); + getGroupPath(canvas, paint); drawGroup(canvas, paint, opacity); releaseCachedPath(); } @@ -175,7 +168,7 @@ public boolean run(VirtualNode node) { }); } - protected Path getGroupPath(Canvas canvas, Paint paint) { + Path getGroupPath(Canvas canvas, Paint paint) { pushGlyphContext(); Path groupPath = super.getPath(canvas, paint); popGlyphContext(); From 6914e962c886bab67ba69c9c6051d509044de144 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 15:02:52 +0300 Subject: [PATCH 043/198] Optimize GlyphContext memory usage and computation. --- .../java/com/horcrux/svg/GlyphContext.java | 163 +++++++----------- 1 file changed, 66 insertions(+), 97 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 54a9d44fb..b4eb9b53b 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -31,16 +31,15 @@ class GlyphContext { private ArrayList> mRotationsContext; private ArrayList> mDeltaXsContext; private ArrayList> mDeltaYsContext; - private ArrayList mRotationIndexContext; - private ArrayList mDeltaXIndexContext; - private ArrayList mDeltaYIndexContext; private ArrayList mFontContext; - private ArrayList mLocationContext; private ArrayList mRotationContext; private ArrayList mDeltaXContext; private ArrayList mDeltaYContext; + private ArrayList mRotations; private ArrayList mXContext; private ArrayList mYContext; + private ArrayList mDeltaXs; + private ArrayList mDeltaYs; private @Nonnull PointF mCurrentLocation; private @Nonnull PointF mCurrentDelta; @@ -49,32 +48,27 @@ class GlyphContext { private float mWidth; private float mHeight; private float mRotation; - private int mContextLength; - private int mDeltaXIndex = -1; - private int mDeltaYIndex = -1; - private int mRotationIndex = -1; private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_FONT_SIZE = 12f; private static final float DEFAULT_LETTER_SPACING = 0f; GlyphContext(float scale, float width, float height) { - mRotationIndexContext = new ArrayList<>(); - mDeltaXIndexContext = new ArrayList<>(); - mDeltaYIndexContext = new ArrayList<>(); mXPositionsContext = new ArrayList<>(); mYPositionsContext = new ArrayList<>(); mRotationsContext = new ArrayList<>(); mRotationContext = new ArrayList<>(); - mLocationContext = new ArrayList<>(); mDeltaXsContext = new ArrayList<>(); mDeltaYsContext = new ArrayList<>(); mDeltaXContext = new ArrayList<>(); mDeltaYContext = new ArrayList<>(); mFontContext = new ArrayList<>(); + mRotations = new ArrayList<>(); mXContext = new ArrayList<>(); mYContext = new ArrayList<>(); + mDeltaXs = new ArrayList<>(); + mDeltaYs = new ArrayList<>(); mCurrentLocation = new PointF(); mCurrentDelta = new PointF(); @@ -85,24 +79,21 @@ class GlyphContext { } void pushContext(@Nullable ReadableMap font) { - mCurrentLocation = clonePointF(mCurrentLocation); - mXPositionsContext.add(new ArrayList()); mYPositionsContext.add(new ArrayList()); - mRotationIndexContext.add(mRotationIndex); - mLocationContext.add(mCurrentLocation); - mDeltaXIndexContext.add(mDeltaXIndex); - mDeltaYIndexContext.add(mDeltaYIndex); + mDeltaXContext.add(mCurrentDelta.x); + mDeltaYContext.add(mCurrentDelta.y); mXContext.add(mCurrentLocation.x); mYContext.add(mCurrentLocation.y); + mRotationsContext.add(mRotations); + mRotationContext.add(mRotation); + mDeltaXsContext.add(mDeltaXs); + mDeltaYsContext.add(mDeltaYs); mFontContext.add(font); - mContextLength++; } void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { - mCurrentLocation = clonePointF(mCurrentLocation); - if (positionX != null) { ArrayList list = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); mCurrentLocation.x = PropHelper.fromPercentageToFloat(list.get(0), mWidth, 0, mScale); @@ -121,32 +112,28 @@ void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nu ArrayList rotations = getFloatArrayListFromReadableArray(rotate); if (rotations.size() != 0) { - mRotationIndex = mRotationsContext.size(); - mRotationsContext.add(rotations); mRotation = rotations.get(0); - mRotationContext.add(mRotation); + mRotations = rotations; } ArrayList deltaXs = getFloatArrayListFromReadableArray(deltaX); if (deltaXs.size() != 0) { - mDeltaXIndex = mDeltaXsContext.size(); - mDeltaXContext.add(mCurrentDelta.x); - mDeltaXsContext.add(deltaXs); + mDeltaXs = deltaXs; } ArrayList deltaYs = getFloatArrayListFromReadableArray(deltaY); if (deltaYs.size() != 0) { - mDeltaYIndex = mDeltaYsContext.size(); - mDeltaYContext.add(mCurrentDelta.y); - mDeltaYsContext.add(deltaYs); + mDeltaYs = deltaYs; } - mRotationIndexContext.add(mRotationIndex); - mLocationContext.add(mCurrentLocation); - mDeltaXIndexContext.add(mDeltaXIndex); - mDeltaYIndexContext.add(mDeltaYIndex); + mDeltaXContext.add(mCurrentDelta.x); + mDeltaYContext.add(mCurrentDelta.y); mXContext.add(mCurrentLocation.x); mYContext.add(mCurrentLocation.y); + mRotationsContext.add(mRotations); + mRotationContext.add(mRotation); + mDeltaXsContext.add(mDeltaXs); + mDeltaYsContext.add(mDeltaYs); mFontContext.add(font); mContextLength++; @@ -155,62 +142,51 @@ void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nu void popContext() { mContextLength--; - float dx = mDeltaXIndex > -1 ? mDeltaXContext.get(mDeltaXIndex) : 0f; - float dy = mDeltaYIndex > -1 ? mDeltaYContext.get(mDeltaYIndex) : 0f; + float dx = mDeltaXContext.get(mContextLength); + float dy = mDeltaYContext.get(mContextLength); float x = mXContext.get(mContextLength); float y = mYContext.get(mContextLength); - mRotationIndexContext.remove(mContextLength); - mDeltaXIndexContext.remove(mContextLength); - mDeltaYIndexContext.remove(mContextLength); mXPositionsContext.remove(mContextLength); mYPositionsContext.remove(mContextLength); - mLocationContext.remove(mContextLength); - mFontContext.remove(mContextLength); - mXContext.remove(mContextLength); - mYContext.remove(mContextLength); - if (mRotationIndex == mContextLength) { - mRotationsContext.remove(mRotationIndex); - mRotationContext.remove(mRotationIndex); - } + mRotationsContext.remove(mContextLength); + mRotationContext.remove(mContextLength); - if (mDeltaXIndex == mContextLength) { - mDeltaXsContext.remove(mDeltaXIndex); - mDeltaXContext.remove(mDeltaXIndex); - } + mDeltaXsContext.remove(mContextLength); + mDeltaXContext.remove(mContextLength); - if (mDeltaYIndex == mContextLength) { - mDeltaYsContext.remove(mDeltaYIndex); - mDeltaYContext.remove(mDeltaYIndex); - } + mDeltaYsContext.remove(mContextLength); + mDeltaYContext.remove(mContextLength); + + mFontContext.remove(mContextLength); + + mXContext.remove(mContextLength); + mYContext.remove(mContextLength); if (mContextLength != 0) { - mRotationIndex = mRotationIndexContext.get(mContextLength - 1); - mCurrentLocation = mLocationContext.get(mContextLength - 1); - mDeltaXIndex = mDeltaXIndexContext.get(mContextLength - 1); - mDeltaYIndex = mDeltaYIndexContext.get(mContextLength - 1); + int index = mContextLength - 1; - mXContext.set(mContextLength - 1, x); - mYContext.set(mContextLength - 1, y); + mDeltaXContext.set(index, dx); + mDeltaYContext.set(index, dy); - if (mDeltaXIndex > -1) { - mDeltaXContext.set(mDeltaXIndex, dx); - } - if (mDeltaYIndex > -1) { - mDeltaYContext.set(mDeltaYIndex, dy); - } + mXContext.set(index, x); + mYContext.set(index, y); mCurrentLocation.x = x; mCurrentLocation.y = y; + mCurrentDelta.x = dx; mCurrentDelta.y = dy; - mRotation = mRotationIndex > -1 ? mRotationContext.get(mRotationIndex) : 0f; + mRotations = mRotationsContext.get(index); + mRotation = mRotationContext.get(index); + + mDeltaXs = mDeltaXsContext.get(index); + mDeltaYs = mDeltaYsContext.get(index); } else { mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; - mDeltaXIndex = mDeltaYIndex = mRotationIndex = -1; mCurrentLocation.x = mCurrentLocation.y = 0; } } @@ -240,33 +216,29 @@ private void resetDelta(ArrayList context) { } PointF getNextGlyphDelta() { - if (mDeltaXIndex > -1) { - mCurrentDelta.x = getNextDelta(mDeltaXsContext, mDeltaXContext); - } - if (mDeltaYIndex > -1) { - mCurrentDelta.y = getNextDelta(mDeltaYsContext, mDeltaYContext); - } - + mCurrentDelta.x = getNextDelta(mDeltaXsContext, mDeltaXContext); + mCurrentDelta.y = getNextDelta(mDeltaYsContext, mDeltaYContext); return mCurrentDelta; } float getNextGlyphRotation() { - if (mRotationIndex > -1) { - int index = mRotationsContext.size() - 1; - int top = index; + int index = mRotationsContext.size() - 1; + List prev = null; + int top = index; - for (; index >= 0; index--) { - List rotations = mRotationsContext.get(index); + for (; index >= 0; index--) { + List rotations = mRotationsContext.get(index); - if (rotations.size() != 0) { - float val = rotations.remove(0); - mRotationContext.set(index, val); + if (prev != rotations && rotations.size() != 0) { + float val = rotations.remove(0); + mRotationContext.set(index, val); - if (index == top) { - mRotation = val; - } + if (index == top) { + mRotation = val; } } + + prev = rotations; } return mRotation; @@ -290,23 +262,24 @@ private boolean hasNext(List> context, int index) { private float getNextDelta(List> lists, List contexts) { int index = lists.size() - 1; + List prev = null; int top = index; - float delta = index > -1 ? contexts.get(index) : 0; + float delta = contexts.get(top); for (; index >= 0; index--) { List deltas = lists.get(index); - if (deltas.size() != 0) { + if (prev != deltas && deltas.size() != 0) { float val = deltas.remove(0); if (top == index) { - float current = contexts.get(index); - float acc = current + val * mScale; - contexts.set(index, acc); - delta = acc; + delta += val * mScale; + contexts.set(index, delta); } } + + prev = deltas; } return delta; @@ -416,8 +389,4 @@ private ArrayList getFloatArrayListFromReadableArray(ReadableArray readab return arrayList; } - - private PointF clonePointF(PointF point) { - return new PointF(point.x, point.y); - } } From ffaca06f4de9e0ee0526ad588407e7d6306b6692 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 17:56:04 +0300 Subject: [PATCH 044/198] Optimize GlyphContext memory usage and computation. --- .../java/com/horcrux/svg/GlyphContext.java | 102 +++++------------- .../java/com/horcrux/svg/GroupShadowNode.java | 4 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 17 ++- 3 files changed, 36 insertions(+), 87 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index b4eb9b53b..0cc876345 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -33,17 +33,14 @@ class GlyphContext { private ArrayList> mDeltaYsContext; private ArrayList mFontContext; private ArrayList mRotationContext; - private ArrayList mDeltaXContext; - private ArrayList mDeltaYContext; private ArrayList mRotations; - private ArrayList mXContext; - private ArrayList mYContext; private ArrayList mDeltaXs; private ArrayList mDeltaYs; private @Nonnull PointF mCurrentLocation; private @Nonnull PointF mCurrentDelta; + private int top = -1; private float mScale; private float mWidth; private float mHeight; @@ -61,12 +58,8 @@ class GlyphContext { mRotationContext = new ArrayList<>(); mDeltaXsContext = new ArrayList<>(); mDeltaYsContext = new ArrayList<>(); - mDeltaXContext = new ArrayList<>(); - mDeltaYContext = new ArrayList<>(); mFontContext = new ArrayList<>(); mRotations = new ArrayList<>(); - mXContext = new ArrayList<>(); - mYContext = new ArrayList<>(); mDeltaXs = new ArrayList<>(); mDeltaYs = new ArrayList<>(); @@ -81,22 +74,18 @@ class GlyphContext { void pushContext(@Nullable ReadableMap font) { mXPositionsContext.add(new ArrayList()); mYPositionsContext.add(new ArrayList()); - mDeltaXContext.add(mCurrentDelta.x); - mDeltaYContext.add(mCurrentDelta.y); - mXContext.add(mCurrentLocation.x); - mYContext.add(mCurrentLocation.y); mRotationsContext.add(mRotations); mRotationContext.add(mRotation); mDeltaXsContext.add(mDeltaXs); mDeltaYsContext.add(mDeltaYs); mFontContext.add(font); mContextLength++; + top++; } void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { if (positionX != null) { ArrayList list = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); - mCurrentLocation.x = PropHelper.fromPercentageToFloat(list.get(0), mWidth, 0, mScale); mXPositionsContext.add(list); } else { mXPositionsContext.add(new ArrayList()); @@ -104,7 +93,6 @@ void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nu if (positionY != null) { ArrayList list = new ArrayList<>(Arrays.asList(positionY.trim().split("\\s+"))); - mCurrentLocation.y = PropHelper.fromPercentageToFloat(list.get(0), mHeight, 0, mScale); mYPositionsContext.add(list); } else { mYPositionsContext.add(new ArrayList()); @@ -126,27 +114,18 @@ void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nu mDeltaYs = deltaYs; } - mDeltaXContext.add(mCurrentDelta.x); - mDeltaYContext.add(mCurrentDelta.y); - mXContext.add(mCurrentLocation.x); - mYContext.add(mCurrentLocation.y); mRotationsContext.add(mRotations); mRotationContext.add(mRotation); mDeltaXsContext.add(mDeltaXs); mDeltaYsContext.add(mDeltaYs); mFontContext.add(font); - mContextLength++; + top++; } void popContext() { mContextLength--; - - float dx = mDeltaXContext.get(mContextLength); - float dy = mDeltaYContext.get(mContextLength); - - float x = mXContext.get(mContextLength); - float y = mYContext.get(mContextLength); + top--; mXPositionsContext.remove(mContextLength); mYPositionsContext.remove(mContextLength); @@ -155,76 +134,49 @@ void popContext() { mRotationContext.remove(mContextLength); mDeltaXsContext.remove(mContextLength); - mDeltaXContext.remove(mContextLength); - mDeltaYsContext.remove(mContextLength); - mDeltaYContext.remove(mContextLength); mFontContext.remove(mContextLength); - mXContext.remove(mContextLength); - mYContext.remove(mContextLength); - if (mContextLength != 0) { - int index = mContextLength - 1; - - mDeltaXContext.set(index, dx); - mDeltaYContext.set(index, dy); - - mXContext.set(index, x); - mYContext.set(index, y); - - mCurrentLocation.x = x; - mCurrentLocation.y = y; - - mCurrentDelta.x = dx; - mCurrentDelta.y = dy; - - mRotations = mRotationsContext.get(index); - mRotation = mRotationContext.get(index); - - mDeltaXs = mDeltaXsContext.get(index); - mDeltaYs = mDeltaYsContext.get(index); + mRotations = mRotationsContext.get(top); + mRotation = mRotationContext.get(top); + mDeltaXs = mDeltaXsContext.get(top); + mDeltaYs = mDeltaYsContext.get(top); } else { mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; mCurrentLocation.x = mCurrentLocation.y = 0; } } - PointF getNextGlyphPoint(float offset, float glyphWidth) { + PointF getNextGlyphPoint(float glyphPosition, float glyphWidth, float startOffset, float kerningValue) { if (hasNext(mXPositionsContext)) { - mCurrentLocation.x = PropHelper.fromPercentageToFloat(getNextString(mXPositionsContext), mWidth, 0, mScale); - resetDelta(mDeltaXContext); + float acc = glyphPosition - startOffset; + String nextString = getNextString(mXPositionsContext); + float x = PropHelper.fromPercentageToFloat(nextString, mWidth, 0, mScale); + mCurrentLocation.x = acc + x + startOffset + kerningValue + glyphWidth; mCurrentDelta.x = 0; + } else { + mCurrentLocation.x += glyphWidth + kerningValue; } if (hasNext(mYPositionsContext)) { - mCurrentLocation.y = PropHelper.fromPercentageToFloat(getNextString(mYPositionsContext), mHeight, 0, mScale); - resetDelta(mDeltaYContext); + String nextString = getNextString(mYPositionsContext); + mCurrentLocation.y = PropHelper.fromPercentageToFloat(nextString, mHeight, 0, mScale); mCurrentDelta.y = 0; } - mXContext.set(mXContext.size() - 1, mCurrentLocation.x + offset + glyphWidth); - mYContext.set(mYContext.size() - 1, mCurrentLocation.y); - - return new PointF(mCurrentLocation.x + offset, mCurrentLocation.y); - } - - private void resetDelta(ArrayList context) { - for (int i = context.size() - 1; i >= 0; i--) { - context.set(i, 0f); - } + return new PointF(mCurrentLocation.x + startOffset - glyphWidth, mCurrentLocation.y); } PointF getNextGlyphDelta() { - mCurrentDelta.x = getNextDelta(mDeltaXsContext, mDeltaXContext); - mCurrentDelta.y = getNextDelta(mDeltaYsContext, mDeltaYContext); + mCurrentDelta.x = getNextDelta(mDeltaXsContext, mCurrentDelta.x); + mCurrentDelta.y = getNextDelta(mDeltaYsContext, mCurrentDelta.y); return mCurrentDelta; } float getNextGlyphRotation() { - int index = mRotationsContext.size() - 1; List prev = null; - int top = index; + int index = top; for (; index >= 0; index--) { List rotations = mRotationsContext.get(index); @@ -245,7 +197,7 @@ float getNextGlyphRotation() { } private boolean hasNext(List> context) { - int index = mContextLength - 1; + int index = top; for (; index >= 0; index--) { if (hasNext(context, index)) { @@ -260,12 +212,11 @@ private boolean hasNext(List> context, int index) { return context.get(index).size() != 0; } - private float getNextDelta(List> lists, List contexts) { - int index = lists.size() - 1; + private float getNextDelta(List> lists, float current) { List prev = null; - int top = index; + int index = top; - float delta = contexts.get(top); + float delta = current; for (; index >= 0; index--) { List deltas = lists.get(index); @@ -275,7 +226,6 @@ private float getNextDelta(List> lists, List contex if (top == index) { delta += val * mScale; - contexts.set(index, delta); } } @@ -286,9 +236,9 @@ private float getNextDelta(List> lists, List contex } private String getNextString(ArrayList> lists) { - int index = lists.size() - 1; boolean valueSet = false; String value = ""; + int index = top; for (; index >= 0; index--) { List list = lists.get(index); diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 332ce5e71..5919c6a96 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -92,8 +92,8 @@ ReadableMap getFontFromContext() { return getTextRoot().getGlyphContext().getGlyphFont(); } - PointF getGlyphPointFromContext(float offset, float glyphWidth) { - return getTextRoot().getGlyphContext().getNextGlyphPoint(offset, glyphWidth); + PointF getGlyphPointFromContext(float glyphPosition, float glyphWidth, float startOffset, float kerningValue) { + return getTextRoot().getGlyphContext().getNextGlyphPoint(glyphPosition, glyphWidth, startOffset, kerningValue); } PointF getGlyphDeltaFromContext() { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 3469fb3d6..f660a3d96 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -129,6 +129,8 @@ private Path getLinePath(String line, Paint paint) { } } + startOffset += getTextAnchorShift(textMeasure); + Path glyph; float width; Matrix matrix; @@ -138,10 +140,10 @@ private Path getLinePath(String line, Paint paint) { float glyphRotation; String previous = ""; float previousWidth = 0; + float glyphPosition = 0; char[] chars = line.toCharArray(); - double kerningValue = font.getDouble(PROP_KERNING) * mScale; + float kerningValue = (float) (font.getDouble(PROP_KERNING) * mScale); boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); - float glyphPosition = startOffset + getTextAnchorShift(textMeasure); for (int index = 0; index < length; index++) { glyph = new Path(); @@ -149,20 +151,17 @@ private Path getLinePath(String line, Paint paint) { paint.getTextPath(current, 0, 1, 0, 0, glyph); width = paint.measureText(current) * renderMethodScaling; - if (isKerningValueSet) { - glyphPosition += kerningValue; - } else { + if (!isKerningValueSet) { float bothWidth = paint.measureText(previous + current) * renderMethodScaling; - float kerning = bothWidth - previousWidth - width; - glyphPosition += kerning; + kerningValue = bothWidth - previousWidth - width; previousWidth = width; previous = current; } - glyphPoint = getGlyphPointFromContext(glyphPosition, width); + glyphPoint = getGlyphPointFromContext(glyphPosition, width, startOffset, kerningValue); glyphRotation = getNextGlyphRotationFromContext(); glyphDelta = getGlyphDeltaFromContext(); - glyphPosition += width; + glyphPosition = glyphPoint.x; matrix = new Matrix(); if (textPath != null) { From 3551f7d994852dbc4d7907ead7a07611435457a7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 18:28:28 +0300 Subject: [PATCH 045/198] Optimize GlyphContext memory usage and computation. --- .../java/com/horcrux/svg/GlyphContext.java | 12 +++++----- .../java/com/horcrux/svg/GroupShadowNode.java | 4 ++-- .../java/com/horcrux/svg/TSpanShadowNode.java | 22 +++++++++---------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 0cc876345..babb9f8d4 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -149,15 +149,11 @@ void popContext() { } } - PointF getNextGlyphPoint(float glyphPosition, float glyphWidth, float startOffset, float kerningValue) { + PointF getNextGlyphPoint(float glyphWidth) { if (hasNext(mXPositionsContext)) { - float acc = glyphPosition - startOffset; String nextString = getNextString(mXPositionsContext); - float x = PropHelper.fromPercentageToFloat(nextString, mWidth, 0, mScale); - mCurrentLocation.x = acc + x + startOffset + kerningValue + glyphWidth; + mCurrentLocation.x = PropHelper.fromPercentageToFloat(nextString, mWidth, 0, mScale); mCurrentDelta.x = 0; - } else { - mCurrentLocation.x += glyphWidth + kerningValue; } if (hasNext(mYPositionsContext)) { String nextString = getNextString(mYPositionsContext); @@ -165,7 +161,9 @@ PointF getNextGlyphPoint(float glyphPosition, float glyphWidth, float startOffse mCurrentDelta.y = 0; } - return new PointF(mCurrentLocation.x + startOffset - glyphWidth, mCurrentLocation.y); + mCurrentLocation.x += glyphWidth; + + return mCurrentLocation; } PointF getNextGlyphDelta() { diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 5919c6a96..ea46bdc21 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -92,8 +92,8 @@ ReadableMap getFontFromContext() { return getTextRoot().getGlyphContext().getGlyphFont(); } - PointF getGlyphPointFromContext(float glyphPosition, float glyphWidth, float startOffset, float kerningValue) { - return getTextRoot().getGlyphContext().getNextGlyphPoint(glyphPosition, glyphWidth, startOffset, kerningValue); + PointF getGlyphPointFromContext(float glyphWidth) { + return getTextRoot().getGlyphContext().getNextGlyphPoint(glyphWidth); } PointF getGlyphDeltaFromContext() { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index f660a3d96..aede63061 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -140,7 +140,6 @@ private Path getLinePath(String line, Paint paint) { float glyphRotation; String previous = ""; float previousWidth = 0; - float glyphPosition = 0; char[] chars = line.toCharArray(); float kerningValue = (float) (font.getDouble(PROP_KERNING) * mScale); boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); @@ -158,17 +157,18 @@ private Path getLinePath(String line, Paint paint) { previous = current; } - glyphPoint = getGlyphPointFromContext(glyphPosition, width, startOffset, kerningValue); + glyphPoint = getGlyphPointFromContext(width + kerningValue); glyphRotation = getNextGlyphRotationFromContext(); glyphDelta = getGlyphDeltaFromContext(); - glyphPosition = glyphPoint.x; matrix = new Matrix(); + float x = startOffset + glyphPoint.x + glyphDelta.x - width; + if (textPath != null) { float halfway = width / 2; - float midpoint = glyphPoint.x + glyphDelta.x + halfway; + float midpoint = x + halfway; - if (midpoint > distance ) { + if (midpoint > distance) { break; } else if (midpoint < 0) { continue; @@ -181,10 +181,8 @@ private Path getLinePath(String line, Paint paint) { matrix.preScale(renderMethodScaling, 1); matrix.postTranslate(0, glyphPoint.y); } else { - matrix.setTranslate( - glyphPoint.x + glyphDelta.x, - glyphPoint.y + glyphDelta.y - ); + float y = glyphPoint.y + glyphDelta.y; + matrix.setTranslate(x, y); } matrix.preRotate(glyphRotation); @@ -200,8 +198,8 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint) { paint.setTextAlign(Paint.Align.LEFT); - float fontSize = (float)font.getDouble(PROP_FONT_SIZE) * mScale; - float letterSpacing = (float)font.getDouble(PROP_LETTER_SPACING) * mScale; + float fontSize = (float) font.getDouble(PROP_FONT_SIZE) * mScale; + float letterSpacing = (float) font.getDouble(PROP_LETTER_SPACING) * mScale; paint.setTextSize(fontSize); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { @@ -254,7 +252,7 @@ private void setupTextPath() { while (parent != null) { if (parent.getClass() == TextPathShadowNode.class) { - textPath = (TextPathShadowNode)parent; + textPath = (TextPathShadowNode) parent; break; } else if (!(parent instanceof TextShadowNode)) { break; From 307dc59c46af53539671887e0d9db955e735dc57 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 18:59:30 +0300 Subject: [PATCH 046/198] =?UTF-8?q?Fix=20propagation=20of=20the=20?= =?UTF-8?q?=E2=80=98x=E2=80=99=20and=20=E2=80=98y=E2=80=99=20attributes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/horcrux/svg/GlyphContext.java | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index babb9f8d4..53ff4087b 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; import javax.annotation.Nonnull; @@ -36,6 +35,8 @@ class GlyphContext { private ArrayList mRotations; private ArrayList mDeltaXs; private ArrayList mDeltaYs; + private ArrayList mXs; + private ArrayList mYs; private @Nonnull PointF mCurrentLocation; private @Nonnull PointF mCurrentDelta; @@ -62,6 +63,8 @@ class GlyphContext { mRotations = new ArrayList<>(); mDeltaXs = new ArrayList<>(); mDeltaYs = new ArrayList<>(); + mXs = new ArrayList<>(); + mYs = new ArrayList<>(); mCurrentLocation = new PointF(); mCurrentDelta = new PointF(); @@ -78,6 +81,8 @@ void pushContext(@Nullable ReadableMap font) { mRotationContext.add(mRotation); mDeltaXsContext.add(mDeltaXs); mDeltaYsContext.add(mDeltaYs); + mXPositionsContext.add(mXs); + mYPositionsContext.add(mYs); mFontContext.add(font); mContextLength++; top++; @@ -85,17 +90,11 @@ void pushContext(@Nullable ReadableMap font) { void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { if (positionX != null) { - ArrayList list = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); - mXPositionsContext.add(list); - } else { - mXPositionsContext.add(new ArrayList()); + mXs = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); } if (positionY != null) { - ArrayList list = new ArrayList<>(Arrays.asList(positionY.trim().split("\\s+"))); - mYPositionsContext.add(list); - } else { - mYPositionsContext.add(new ArrayList()); + mYs = new ArrayList<>(Arrays.asList(positionY.trim().split("\\s+"))); } ArrayList rotations = getFloatArrayListFromReadableArray(rotate); @@ -118,6 +117,8 @@ void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nu mRotationContext.add(mRotation); mDeltaXsContext.add(mDeltaXs); mDeltaYsContext.add(mDeltaYs); + mXPositionsContext.add(mXs); + mYPositionsContext.add(mYs); mFontContext.add(font); mContextLength++; top++; @@ -143,6 +144,8 @@ void popContext() { mRotation = mRotationContext.get(top); mDeltaXs = mDeltaXsContext.get(top); mDeltaYs = mDeltaYsContext.get(top); + mXs = mXPositionsContext.get(top); + mYs = mYPositionsContext.get(top); } else { mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; mCurrentLocation.x = mCurrentLocation.y = 0; @@ -150,16 +153,8 @@ void popContext() { } PointF getNextGlyphPoint(float glyphWidth) { - if (hasNext(mXPositionsContext)) { - String nextString = getNextString(mXPositionsContext); - mCurrentLocation.x = PropHelper.fromPercentageToFloat(nextString, mWidth, 0, mScale); - mCurrentDelta.x = 0; - } - if (hasNext(mYPositionsContext)) { - String nextString = getNextString(mYPositionsContext); - mCurrentLocation.y = PropHelper.fromPercentageToFloat(nextString, mHeight, 0, mScale); - mCurrentDelta.y = 0; - } + mCurrentLocation.x = getGlyphPosition(mXPositionsContext, mWidth, mCurrentLocation.x, mCurrentDelta, true); + mCurrentLocation.y = getGlyphPosition(mYPositionsContext, mHeight, mCurrentLocation.y, mCurrentDelta, false); mCurrentLocation.x += glyphWidth; @@ -194,27 +189,10 @@ float getNextGlyphRotation() { return mRotation; } - private boolean hasNext(List> context) { - int index = top; - - for (; index >= 0; index--) { - if (hasNext(context, index)) { - return true; - } - } - - return false; - } - - private boolean hasNext(List> context, int index) { - return context.get(index).size() != 0; - } - private float getNextDelta(List> lists, float current) { List prev = null; - int index = top; - float delta = current; + int index = top; for (; index >= 0; index--) { List deltas = lists.get(index); @@ -233,22 +211,28 @@ private float getNextDelta(List> lists, float current) { return delta; } - private String getNextString(ArrayList> lists) { - boolean valueSet = false; - String value = ""; + private float getGlyphPosition(ArrayList> lists, float dim, float current, PointF mCurrentDelta, boolean isX) { + List prev = null; + Float value = current; int index = top; for (; index >= 0; index--) { List list = lists.get(index); - if (list.size() != 0) { + if (prev != list && list.size() != 0) { String val = list.remove(0); - if (!valueSet) { - valueSet = true; - value = val; + if (top == index) { + value = PropHelper.fromPercentageToFloat(val, dim, 0, mScale); + if (isX) { + mCurrentDelta.x = 0; + } else { + mCurrentDelta.y = 0; + } } } + + prev = list; } return value; From e4ad4e3daf913385de1755802830351a14b81163 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 19:14:10 +0300 Subject: [PATCH 047/198] Remove extra position context add call. --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 53ff4087b..221423364 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -75,8 +75,6 @@ class GlyphContext { } void pushContext(@Nullable ReadableMap font) { - mXPositionsContext.add(new ArrayList()); - mYPositionsContext.add(new ArrayList()); mRotationsContext.add(mRotations); mRotationContext.add(mRotation); mDeltaXsContext.add(mDeltaXs); From caffb0e16ab632534e84a773debdb345e4c6165c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 19:39:13 +0300 Subject: [PATCH 048/198] Refactor and simplify GlyphContext --- .../java/com/horcrux/svg/GlyphContext.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 221423364..319fba823 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -151,17 +151,15 @@ void popContext() { } PointF getNextGlyphPoint(float glyphWidth) { - mCurrentLocation.x = getGlyphPosition(mXPositionsContext, mWidth, mCurrentLocation.x, mCurrentDelta, true); - mCurrentLocation.y = getGlyphPosition(mYPositionsContext, mHeight, mCurrentLocation.y, mCurrentDelta, false); - + mCurrentLocation.x = getGlyphPosition(true); + mCurrentLocation.y = getGlyphPosition(false); mCurrentLocation.x += glyphWidth; - return mCurrentLocation; } PointF getNextGlyphDelta() { - mCurrentDelta.x = getNextDelta(mDeltaXsContext, mCurrentDelta.x); - mCurrentDelta.y = getNextDelta(mDeltaYsContext, mCurrentDelta.y); + mCurrentDelta.x = getNextDelta(true); + mCurrentDelta.y = getNextDelta(false); return mCurrentDelta; } @@ -187,12 +185,12 @@ float getNextGlyphRotation() { return mRotation; } - private float getNextDelta(List> lists, float current) { + private float getNextDelta(boolean isX) { + ArrayList> lists = isX ? mDeltaXsContext : mDeltaYsContext; + float delta = isX ? mCurrentDelta.x : mCurrentDelta.y; List prev = null; - float delta = current; - int index = top; - for (; index >= 0; index--) { + for (int index = top; index >= 0; index--) { List deltas = lists.get(index); if (prev != deltas && deltas.size() != 0) { @@ -209,19 +207,20 @@ private float getNextDelta(List> lists, float current) { return delta; } - private float getGlyphPosition(ArrayList> lists, float dim, float current, PointF mCurrentDelta, boolean isX) { + private float getGlyphPosition(boolean isX) { + ArrayList> lists = isX ? mXPositionsContext : mYPositionsContext; + float value = isX ? mCurrentLocation.x : mCurrentLocation.y; List prev = null; - Float value = current; - int index = top; - for (; index >= 0; index--) { + for (int index = top; index >= 0; index--) { List list = lists.get(index); if (prev != list && list.size() != 0) { String val = list.remove(0); if (top == index) { - value = PropHelper.fromPercentageToFloat(val, dim, 0, mScale); + float relative = isX ? mWidth : mHeight; + value = PropHelper.fromPercentageToFloat(val, relative, 0, mScale); if (isX) { mCurrentDelta.x = 0; } else { @@ -250,7 +249,7 @@ ReadableMap getGlyphFont() { String fontStyle = null; // TODO: add support for other length units - for (int index = mContextLength - 1; index >= 0; index--) { + for (int index = top; index >= 0; index--) { ReadableMap font = mFontContext.get(index); if (fontFamily == null && font.hasKey("fontFamily")) { From 4a757310dfb18c13fd254671135a6b108452f07e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 19:48:09 +0300 Subject: [PATCH 049/198] Refactor and simplify GlyphContext --- .../java/com/horcrux/svg/GlyphContext.java | 61 +++++++------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 319fba823..0c0b87c60 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -24,51 +24,32 @@ import javax.annotation.Nullable; class GlyphContext { - - private ArrayList> mXPositionsContext; - private ArrayList> mYPositionsContext; - private ArrayList> mRotationsContext; - private ArrayList> mDeltaXsContext; - private ArrayList> mDeltaYsContext; - private ArrayList mFontContext; - private ArrayList mRotationContext; - private ArrayList mRotations; - private ArrayList mDeltaXs; - private ArrayList mDeltaYs; - private ArrayList mXs; - private ArrayList mYs; - - private @Nonnull PointF mCurrentLocation; - private @Nonnull PointF mCurrentDelta; - - private int top = -1; - private float mScale; - private float mWidth; - private float mHeight; - private float mRotation; - private int mContextLength; - private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_FONT_SIZE = 12f; private static final float DEFAULT_LETTER_SPACING = 0f; - GlyphContext(float scale, float width, float height) { - mXPositionsContext = new ArrayList<>(); - mYPositionsContext = new ArrayList<>(); - mRotationsContext = new ArrayList<>(); - mRotationContext = new ArrayList<>(); - mDeltaXsContext = new ArrayList<>(); - mDeltaYsContext = new ArrayList<>(); - mFontContext = new ArrayList<>(); - mRotations = new ArrayList<>(); - mDeltaXs = new ArrayList<>(); - mDeltaYs = new ArrayList<>(); - mXs = new ArrayList<>(); - mYs = new ArrayList<>(); - - mCurrentLocation = new PointF(); - mCurrentDelta = new PointF(); + private ArrayList> mXPositionsContext = new ArrayList<>(); + private ArrayList> mYPositionsContext = new ArrayList<>(); + private ArrayList> mRotationsContext = new ArrayList<>(); + private ArrayList> mDeltaXsContext = new ArrayList<>(); + private ArrayList> mDeltaYsContext = new ArrayList<>(); + private ArrayList mFontContext = new ArrayList<>(); + private ArrayList mRotationContext = new ArrayList<>(); + private ArrayList mRotations = new ArrayList<>(); + private @Nonnull PointF mCurrentLocation = new PointF(); + private ArrayList mDeltaXs = new ArrayList<>(); + private ArrayList mDeltaYs = new ArrayList<>(); + private @Nonnull PointF mCurrentDelta = new PointF(); + private ArrayList mXs = new ArrayList<>(); + private ArrayList mYs = new ArrayList<>(); + private int mContextLength; + private float mRotation; + private float mHeight; + private float mWidth; + private float mScale; + private int top = -1; + GlyphContext(float scale, float width, float height) { mHeight = height; mWidth = width; mScale = scale; From 790542614dfbaf9e3011aaf0c0b8bc0a80944efd Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 21:41:02 +0300 Subject: [PATCH 050/198] First implementation attempt at generic em relative units. --- .../src/main/java/com/horcrux/svg/Brush.java | 21 ++++----- .../com/horcrux/svg/CircleShadowNode.java | 15 ++++-- .../java/com/horcrux/svg/GlyphContext.java | 19 ++++++-- .../java/com/horcrux/svg/GroupShadowNode.java | 33 ------------- .../main/java/com/horcrux/svg/PropHelper.java | 13 ++++-- .../java/com/horcrux/svg/TSpanShadowNode.java | 2 +- .../java/com/horcrux/svg/VirtualNode.java | 46 ++++++++++++++++++- 7 files changed, 91 insertions(+), 58 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/Brush.java b/android/src/main/java/com/horcrux/svg/Brush.java index 0fe36f121..9f3b1eadf 100644 --- a/android/src/main/java/com/horcrux/svg/Brush.java +++ b/android/src/main/java/com/horcrux/svg/Brush.java @@ -9,7 +9,6 @@ package com.horcrux.svg; -import android.graphics.BitmapShader; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Matrix; @@ -108,10 +107,10 @@ public void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float op parseGradientStops(mColors, stopsCount, stops, stopsColors, opacity); if (mType == BrushType.LINEAR_GRADIENT) { - float x1 = PropHelper.fromPercentageToFloat(mPoints.getString(0), width, offsetX, scale); - float y1 = PropHelper.fromPercentageToFloat(mPoints.getString(1), height, offsetY, scale); - float x2 = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, offsetX, scale); - float y2 = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, offsetY, scale); + float x1 = PropHelper.fromRelativeToFloat(mPoints.getString(0), width, offsetX, scale, paint.getTextSize()); + float y1 = PropHelper.fromRelativeToFloat(mPoints.getString(1), height, offsetY, scale, paint.getTextSize()); + float x2 = PropHelper.fromRelativeToFloat(mPoints.getString(2), width, offsetX, scale, paint.getTextSize()); + float y2 = PropHelper.fromRelativeToFloat(mPoints.getString(3), height, offsetY, scale, paint.getTextSize()); Shader linearGradient = new LinearGradient( x1, @@ -130,13 +129,13 @@ public void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float op paint.setShader(linearGradient); } else if (mType == BrushType.RADIAL_GRADIENT) { - float rx = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, 0f, scale); - float ry = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, 0f, scale); - float cx = PropHelper.fromPercentageToFloat(mPoints.getString(4), width, offsetX, scale); - float cy = PropHelper.fromPercentageToFloat(mPoints.getString(5), height, offsetY, scale) / (ry / rx); + float rx = PropHelper.fromRelativeToFloat(mPoints.getString(2), width, 0f, scale, paint.getTextSize()); + float ry = PropHelper.fromRelativeToFloat(mPoints.getString(3), height, 0f, scale, paint.getTextSize()); + float cx = PropHelper.fromRelativeToFloat(mPoints.getString(4), width, offsetX, scale, paint.getTextSize()); + float cy = PropHelper.fromRelativeToFloat(mPoints.getString(5), height, offsetY, scale, paint.getTextSize()) / (ry / rx); // TODO: support focus point. - //float fx = PropHelper.fromPercentageToFloat(mPoints.getString(0), width, offsetX, scale); - //float fy = PropHelper.fromPercentageToFloat(mPoints.getString(1), height, offsetY, scale) / (ry / rx); + //float fx = PropHelper.fromRelativeToFloat(mPoints.getString(0), width, offsetX, scale); + //float fy = PropHelper.fromRelativeToFloat(mPoints.getString(1), height, offsetY, scale) / (ry / rx); Shader radialGradient = new RadialGradient( cx, cy, diff --git a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java index c2915e96c..a8a0cf128 100644 --- a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java @@ -20,6 +20,13 @@ */ public class CircleShadowNode extends RenderableShadowNode { + /* + N[1/Sqrt[2], 36] + The inverse of the square root of 2. + Provide enough digits for the 128-bit IEEE quad (36 significant digits). + */ + private static final double M_SQRT1_2l = 0.707106781186547524400844362104849039; + private String mCx; private String mCy; private String mR; @@ -51,10 +58,10 @@ protected Path getPath(Canvas canvas, Paint paint) { float r; if (PropHelper.isPercentage(mR)) { - r = PropHelper.fromPercentageToFloat(mR, 1, 0, 1); - float powX = (float)Math.pow((getCanvasWidth() * r), 2); - float powY = (float)Math.pow((getCanvasHeight() * r), 2); - r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2); + r = PropHelper.fromRelativeToFloat(mR, 1, 0, 1, paint.getTextSize()); + double powX = Math.pow((getCanvasWidth() * r), 2); + double powY = Math.pow((getCanvasHeight() * r), 2); + r = (float) (Math.sqrt(powX + powY) * M_SQRT1_2l); } else { r = Float.parseFloat(mR) * mScale; } diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 0c0b87c60..75591238f 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -24,8 +24,8 @@ import javax.annotation.Nullable; class GlyphContext { + static final float DEFAULT_FONT_SIZE = 12f; private static final float DEFAULT_KERNING = 0f; - private static final float DEFAULT_FONT_SIZE = 12f; private static final float DEFAULT_LETTER_SPACING = 0f; private ArrayList> mXPositionsContext = new ArrayList<>(); @@ -201,7 +201,7 @@ private float getGlyphPosition(boolean isX) { if (top == index) { float relative = isX ? mWidth : mHeight; - value = PropHelper.fromPercentageToFloat(val, relative, 0, mScale); + value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, getFontSize()); if (isX) { mCurrentDelta.x = 0; } else { @@ -216,6 +216,18 @@ private float getGlyphPosition(boolean isX) { return value; } + double getFontSize() { + for (int index = top; index >= 0; index--) { + ReadableMap font = mFontContext.get(index); + + if (mFontContext.get(index).hasKey("fontSize")) { + return font.getDouble("fontSize"); + } + } + + return DEFAULT_FONT_SIZE; + } + ReadableMap getGlyphFont() { float letterSpacing = DEFAULT_LETTER_SPACING; float fontSize = DEFAULT_FONT_SIZE; @@ -279,7 +291,6 @@ ReadableMap getGlyphFont() { } private ArrayList getFloatArrayListFromReadableArray(ReadableArray readableArray) { - float fontSize = (float) getGlyphFont().getDouble("fontSize"); ArrayList arrayList = new ArrayList<>(); if (readableArray != null) { @@ -287,7 +298,7 @@ private ArrayList getFloatArrayListFromReadableArray(ReadableArray readab switch (readableArray.getType(i)) { case String: String val = readableArray.getString(i); - arrayList.add(Float.valueOf(val.substring(0, val.length() - 2)) * fontSize); + arrayList.add((float) (Float.valueOf(val.substring(0, val.length() - 2)) * getFontSize())); break; case Number: diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index ea46bdc21..24213f020 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -29,7 +29,6 @@ class GroupShadowNode extends RenderableShadowNode { @Nullable ReadableMap mFont; private GlyphContext mGlyphContext; - private GroupShadowNode mTextRoot; @ReactProp(name = "font") public void setFont(@Nullable ReadableMap font) { @@ -37,38 +36,6 @@ public void setFont(@Nullable ReadableMap font) { markUpdated(); } - GroupShadowNode getTextRoot() { - GroupShadowNode shadowNode = getShadowNode(GroupShadowNode.class); - if (shadowNode == null) { - return getShadowNode(TextShadowNode.class); - } - return shadowNode; - } - - @android.support.annotation.Nullable - private GroupShadowNode getShadowNode(Class shadowNodeClass) { - if (mTextRoot == null) { - mTextRoot = this; - - while (mTextRoot != null) { - if (mTextRoot.getClass() == shadowNodeClass) { - break; - } - - ReactShadowNode parent = mTextRoot.getParent(); - - if (!(parent instanceof GroupShadowNode)) { - //todo: throw exception here - mTextRoot = null; - } else { - mTextRoot = (GroupShadowNode)parent; - } - } - } - - return mTextRoot; - } - void setupGlyphContext() { mGlyphContext = new GlyphContext(mScale, getCanvasWidth(), getCanvasHeight()); } diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 1e1baa2f3..6cc37475e 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -72,22 +72,29 @@ static int toFloatArray(ReadableArray value, float[] into) { } static private Pattern percentageRegExp = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$"); + static private Pattern emRegExp = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)em$"); /** - * Converts percentage string into actual based on a relative number + * Converts percentage or em string into actual based on a relative number * * @param percentage percentage string * @param relative relative number * @param offset offset number + * @param fontSize current font size * @return actual float based on relative number */ - static float fromPercentageToFloat(String percentage, float relative, float offset, float scale) { + static float fromRelativeToFloat(String percentage, float relative, float offset, float scale, double fontSize) { Matcher matched = percentageRegExp.matcher(percentage); if (matched.matches()) { return Float.valueOf(matched.group(1)) / 100 * relative + offset; } else { - return Float.valueOf(percentage) * scale + offset; + matched = emRegExp.matcher(percentage); + if (matched.matches()) { + return (float) (Float.valueOf(matched.group(1)) * scale * fontSize); + } else { + return Float.valueOf(percentage) * scale + offset; + } } } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index aede63061..8065c98c7 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -121,7 +121,7 @@ private Path getLinePath(String line, Paint paint) { if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - startOffset = PropHelper.fromPercentageToFloat(textPath.getStartOffset(), distance, 0, mScale); + startOffset = PropHelper.fromRelativeToFloat(textPath.getStartOffset(), distance, 0, mScale, getFontSizeFromContext()); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" String method = textPath.getMethod(); // method = "align | stretch" if ("stretch".equals(method)) { diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 72f4a07df..a6c1dd331 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -26,6 +26,8 @@ import javax.annotation.Nullable; +import static com.horcrux.svg.GlyphContext.DEFAULT_FONT_SIZE; + public abstract class VirtualNode extends LayoutShadowNode { protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; @@ -49,6 +51,7 @@ public abstract class VirtualNode extends LayoutShadowNode { private SvgViewShadowNode mSvgShadowNode; private Path mCachedClipPath; + private GroupShadowNode mTextRoot; public VirtualNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; @@ -59,6 +62,45 @@ public boolean isVirtual() { return true; } + GroupShadowNode getTextRoot() { + GroupShadowNode shadowNode = getShadowNode(GroupShadowNode.class); + if (shadowNode == null) { + return getShadowNode(TextShadowNode.class); + } + return shadowNode; + } + + @android.support.annotation.Nullable + private GroupShadowNode getShadowNode(Class shadowNodeClass) { + VirtualNode node = this; + if (mTextRoot == null) { + while (node != null) { + if (node.getClass() == shadowNodeClass) { + mTextRoot = (GroupShadowNode)node; + break; + } + + ReactShadowNode parent = node.getParent(); + + if (!(parent instanceof VirtualNode)) { + node = null; + } else { + node = (VirtualNode)parent; + } + } + } + + return mTextRoot; + } + + double getFontSizeFromContext() { + GroupShadowNode root = getTextRoot(); + if (root == null) { + return DEFAULT_FONT_SIZE; + } + return root.getGlyphContext().getFontSize(); + } + public abstract void draw(Canvas canvas, Paint paint, float opacity); /** @@ -229,11 +271,11 @@ protected SvgViewShadowNode getSvgShadowNode() { } protected float relativeOnWidth(String length) { - return PropHelper.fromPercentageToFloat(length, getCanvasWidth(), 0, mScale); + return PropHelper.fromRelativeToFloat(length, getCanvasWidth(), 0, mScale, getFontSizeFromContext()); } protected float relativeOnHeight(String length) { - return PropHelper.fromPercentageToFloat(length, getCanvasHeight(), 0, mScale); + return PropHelper.fromRelativeToFloat(length, getCanvasHeight(), 0, mScale, getFontSizeFromContext()); } protected float getCanvasWidth() { From 3796b6a53293ba28780fc3b89d2bbebe2b737be6 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 22:10:10 +0300 Subject: [PATCH 051/198] Implement correct calculation of fontSize from ancestors. --- .../java/com/horcrux/svg/GlyphContext.java | 12 +++++- .../java/com/horcrux/svg/GroupShadowNode.java | 2 +- .../java/com/horcrux/svg/TextShadowNode.java | 2 +- .../java/com/horcrux/svg/VirtualNode.java | 40 +++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 75591238f..68dfa8c5e 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -35,6 +35,7 @@ class GlyphContext { private ArrayList> mDeltaYsContext = new ArrayList<>(); private ArrayList mFontContext = new ArrayList<>(); private ArrayList mRotationContext = new ArrayList<>(); + private ArrayList mNodes = new ArrayList<>(); private ArrayList mRotations = new ArrayList<>(); private @Nonnull PointF mCurrentLocation = new PointF(); private ArrayList mDeltaXs = new ArrayList<>(); @@ -55,7 +56,7 @@ class GlyphContext { mScale = scale; } - void pushContext(@Nullable ReadableMap font) { + void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { mRotationsContext.add(mRotations); mRotationContext.add(mRotation); mDeltaXsContext.add(mDeltaXs); @@ -63,11 +64,12 @@ void pushContext(@Nullable ReadableMap font) { mXPositionsContext.add(mXs); mYPositionsContext.add(mYs); mFontContext.add(font); + mNodes.add(node); mContextLength++; top++; } - void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { + void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { if (positionX != null) { mXs = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); } @@ -99,6 +101,7 @@ void pushContext(@Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nu mXPositionsContext.add(mXs); mYPositionsContext.add(mYs); mFontContext.add(font); + mNodes.add(node); mContextLength++; top++; } @@ -117,6 +120,7 @@ void popContext() { mDeltaYsContext.remove(mContextLength); mFontContext.remove(mContextLength); + mNodes.remove(mContextLength); if (mContextLength != 0) { mRotations = mRotationsContext.get(top); @@ -225,6 +229,10 @@ private float getGlyphPosition(boolean isX) { } } + if (top > -1) { + return mNodes.get(0).getFontSizeFromParentContext(); + } + return DEFAULT_FONT_SIZE; } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 24213f020..ef24f8438 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -48,7 +48,7 @@ GlyphContext getGlyphContext() { } protected void pushGlyphContext() { - getTextRoot().getGlyphContext().pushContext(mFont); + getTextRoot().getGlyphContext().pushContext(this, mFont); } protected void popGlyphContext() { diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 7c69b4c88..bfff1fba1 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -178,6 +178,6 @@ Path getGroupPath(Canvas canvas, Paint paint) { @Override protected void pushGlyphContext() { - getTextRoot().getGlyphContext().pushContext(mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY); + getTextRoot().getGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY); } } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index a6c1dd331..fe19d942b 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -51,6 +51,7 @@ public abstract class VirtualNode extends LayoutShadowNode { private SvgViewShadowNode mSvgShadowNode; private Path mCachedClipPath; + private GroupShadowNode mParentTextRoot; private GroupShadowNode mTextRoot; public VirtualNode() { @@ -70,6 +71,37 @@ GroupShadowNode getTextRoot() { return shadowNode; } + GroupShadowNode getParentTextRoot() { + GroupShadowNode shadowNode = getParentShadowNode(GroupShadowNode.class); + if (shadowNode == null) { + return getParentShadowNode(TextShadowNode.class); + } + return shadowNode; + } + + @android.support.annotation.Nullable + private GroupShadowNode getParentShadowNode(Class shadowNodeClass) { + ReactShadowNode node = this.getParent(); + if (mParentTextRoot == null) { + while (node != null) { + if (node.getClass() == shadowNodeClass) { + mParentTextRoot = (GroupShadowNode)node; + break; + } + + ReactShadowNode parent = node.getParent(); + + if (!(parent instanceof VirtualNode)) { + node = null; + } else { + node = parent; + } + } + } + + return mParentTextRoot; + } + @android.support.annotation.Nullable private GroupShadowNode getShadowNode(Class shadowNodeClass) { VirtualNode node = this; @@ -101,6 +133,14 @@ private GroupShadowNode getShadowNode(Class shadowNodeClass) { return root.getGlyphContext().getFontSize(); } + double getFontSizeFromParentContext() { + GroupShadowNode root = getParentTextRoot(); + if (root == null) { + return DEFAULT_FONT_SIZE; + } + return root.getGlyphContext().getFontSize(); + } + public abstract void draw(Canvas canvas, Paint paint, float opacity); /** From ebeb89beb596ad1511664aebfcb8e5962c240f60 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 19 Jul 2017 23:35:34 +0300 Subject: [PATCH 052/198] Improve units and fontSize implementation. Implement support for units in strokewidth based on relativeOnOther. --- .../com/horcrux/svg/CircleShadowNode.java | 14 ++------ .../main/java/com/horcrux/svg/PropHelper.java | 5 ++- .../com/horcrux/svg/RenderableShadowNode.java | 11 ++++--- .../java/com/horcrux/svg/VirtualNode.java | 33 ++++++++++++++++--- lib/extract/extractStroke.js | 4 +-- lib/props.js | 2 +- 6 files changed, 44 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java index a8a0cf128..509194357 100644 --- a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java @@ -20,13 +20,6 @@ */ public class CircleShadowNode extends RenderableShadowNode { - /* - N[1/Sqrt[2], 36] - The inverse of the square root of 2. - Provide enough digits for the 128-bit IEEE quad (36 significant digits). - */ - private static final double M_SQRT1_2l = 0.707106781186547524400844362104849039; - private String mCx; private String mCy; private String mR; @@ -58,12 +51,9 @@ protected Path getPath(Canvas canvas, Paint paint) { float r; if (PropHelper.isPercentage(mR)) { - r = PropHelper.fromRelativeToFloat(mR, 1, 0, 1, paint.getTextSize()); - double powX = Math.pow((getCanvasWidth() * r), 2); - double powY = Math.pow((getCanvasHeight() * r), 2); - r = (float) (Math.sqrt(powX + powY) * M_SQRT1_2l); + r = relativeOnOther(mR); } else { - r = Float.parseFloat(mR) * mScale; + r = Float.parseFloat(mR) * mScale; } path.addCircle(cx, cy, r, Path.Direction.CW); diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 6cc37475e..89841a023 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -72,7 +72,7 @@ static int toFloatArray(ReadableArray value, float[] into) { } static private Pattern percentageRegExp = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$"); - static private Pattern emRegExp = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)em$"); + static private Pattern emRegExp = Pattern.compile("^(\\-?\\.?\\d+(?:\\.\\d+)?)em$"); /** * Converts percentage or em string into actual based on a relative number @@ -93,6 +93,9 @@ static float fromRelativeToFloat(String percentage, float relative, float offset if (matched.matches()) { return (float) (Float.valueOf(matched.group(1)) * scale * fontSize); } else { + if (percentage.endsWith("px")) { + percentage = percentage.substring(0, percentage.length() - 2); + } return Float.valueOf(percentage) * scale + offset; } } diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 90f16b7ca..0e3ed086f 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -54,7 +54,7 @@ abstract public class RenderableShadowNode extends VirtualNode { public @Nullable ReadableArray mStroke; public @Nullable float[] mStrokeDasharray; - public float mStrokeWidth = 1; + public String mStrokeWidth = "1"; public float mStrokeOpacity = 1; public float mStrokeMiterlimit = 4; public float mStrokeDashoffset = 0; @@ -132,8 +132,8 @@ public void setStrokeDashoffset(float strokeWidth) { markUpdated(); } - @ReactProp(name = "strokeWidth", defaultFloat = 1f) - public void setStrokeWidth(float strokeWidth) { + @ReactProp(name = "strokeWidth") + public void setStrokeWidth(String strokeWidth) { mStrokeWidth = strokeWidth; markUpdated(); } @@ -235,7 +235,8 @@ protected boolean setupFillPaint(Paint paint, float opacity) { */ protected boolean setupStrokePaint(Paint paint, float opacity) { paint.reset(); - if (mStrokeWidth == 0 || mStroke == null || mStroke.size() == 0) { + float strokeWidth = relativeOnOther(mStrokeWidth); + if (strokeWidth == 0 || mStroke == null || mStroke.size() == 0) { return false; } @@ -244,7 +245,7 @@ protected boolean setupStrokePaint(Paint paint, float opacity) { paint.setStrokeCap(mStrokeLinecap); paint.setStrokeJoin(mStrokeLinejoin); paint.setStrokeMiter(mStrokeMiterlimit * mScale); - paint.setStrokeWidth(mStrokeWidth * mScale); + paint.setStrokeWidth(strokeWidth); setupPaint(paint, opacity, mStroke); if (mStrokeDasharray != null && mStrokeDasharray.length > 0) { diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index fe19d942b..1a543ebab 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -29,6 +29,12 @@ import static com.horcrux.svg.GlyphContext.DEFAULT_FONT_SIZE; public abstract class VirtualNode extends LayoutShadowNode { + /* + N[1/Sqrt[2], 36] + The inverse of the square root of 2. + Provide enough digits for the 128-bit IEEE quad (36 significant digits). + */ + private static final double M_SQRT1_2l = 0.707106781186547524400844362104849039; protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; @@ -37,6 +43,8 @@ public abstract class VirtualNode extends LayoutShadowNode { protected float mOpacity = 1f; protected float mScaleX = 1f; protected float mScaleY = 1f; + protected double mFontSize = -1; + protected double mParentFontSize = -1; protected Matrix mMatrix = new Matrix(); private int mClipRule; @@ -126,19 +134,29 @@ private GroupShadowNode getShadowNode(Class shadowNodeClass) { } double getFontSizeFromContext() { + if (mFontSize != -1) { + return mFontSize; + } GroupShadowNode root = getTextRoot(); if (root == null) { - return DEFAULT_FONT_SIZE; + mFontSize = DEFAULT_FONT_SIZE; + } else { + mFontSize = root.getGlyphContext().getFontSize(); } - return root.getGlyphContext().getFontSize(); + return mFontSize; } double getFontSizeFromParentContext() { + if (mParentFontSize != -1) { + return mParentFontSize; + } GroupShadowNode root = getParentTextRoot(); if (root == null) { - return DEFAULT_FONT_SIZE; + mParentFontSize = DEFAULT_FONT_SIZE; + } else { + mParentFontSize = root.getGlyphContext().getFontSize(); } - return root.getGlyphContext().getFontSize(); + return mParentFontSize; } public abstract void draw(Canvas canvas, Paint paint, float opacity); @@ -318,6 +336,13 @@ protected float relativeOnHeight(String length) { return PropHelper.fromRelativeToFloat(length, getCanvasHeight(), 0, mScale, getFontSizeFromContext()); } + protected float relativeOnOther(String length) { + double powX = Math.pow((getCanvasWidth()), 2); + double powY = Math.pow((getCanvasHeight()), 2); + float r = (float) (Math.sqrt(powX + powY) * M_SQRT1_2l); + return PropHelper.fromRelativeToFloat(length, r, 0, mScale, getFontSizeFromContext()); + } + protected float getCanvasWidth() { return getSvgShadowNode().getCanvasBounds().width(); } diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 270f76db9..a4c1ebdcd 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -26,7 +26,7 @@ export default function(props, styleProperties) { }); const {stroke} = props; - const strokeWidth = +props.strokeWidth; + const strokeWidth = props.strokeWidth; let strokeDasharray = props.strokeDasharray; if (typeof strokeDasharray === 'string') { @@ -44,7 +44,7 @@ export default function(props, styleProperties) { strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeDasharray: strokeDasharray || null, - strokeWidth: strokeWidth || null, + strokeWidth: strokeWidth || "1", strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, strokeMiterlimit: props.strokeMiterlimit || 4 }; diff --git a/lib/props.js b/lib/props.js index b5b4ea427..6e8d6bdb2 100644 --- a/lib/props.js +++ b/lib/props.js @@ -39,7 +39,7 @@ const definationProps = { const strokeProps = { stroke: PropTypes.string, - strokeWidth: numberProp, + strokeWidth: PropTypes.string, strokeOpacity: numberProp, strokeDasharray: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.string]), strokeDashoffset: numberProp, From a5b9cea451779d250d2df3e112400c767ac4e28a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 20 Jul 2017 00:00:54 +0300 Subject: [PATCH 053/198] Implement support for remaining units. --- .../main/java/com/horcrux/svg/PropHelper.java | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 89841a023..5c08f5134 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -72,31 +72,73 @@ static int toFloatArray(ReadableArray value, float[] into) { } static private Pattern percentageRegExp = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$"); - static private Pattern emRegExp = Pattern.compile("^(\\-?\\.?\\d+(?:\\.\\d+)?)em$"); /** - * Converts percentage or em string into actual based on a relative number + * Converts length string into actual based on a relative number * - * @param percentage percentage string + * + * @param length length string * @param relative relative number * @param offset offset number * @param fontSize current font size * @return actual float based on relative number */ - static float fromRelativeToFloat(String percentage, float relative, float offset, float scale, double fontSize) { - Matcher matched = percentageRegExp.matcher(percentage); - if (matched.matches()) { - return Float.valueOf(matched.group(1)) / 100 * relative + offset; + static float fromRelativeToFloat(String length, float relative, float offset, float scale, double fontSize) { + length = length.trim(); + int percentIndex = length.length() - 1; + if (length.codePointAt(percentIndex) == '%') { + return Float.valueOf(length.substring(0, percentIndex)) / 100 * relative + offset; } else { - matched = emRegExp.matcher(percentage); - if (matched.matches()) { - return (float) (Float.valueOf(matched.group(1)) * scale * fontSize); - } else { - if (percentage.endsWith("px")) { - percentage = percentage.substring(0, percentage.length() - 2); + int twoLetterUnitIndex = length.length() - 2; + if (twoLetterUnitIndex > 0) { + String lastTwo = length.substring(twoLetterUnitIndex); + + float val; + switch (lastTwo) { + case "px": + val = Float.valueOf(length.substring(0, twoLetterUnitIndex)); + break; + + case "em": + val = (float) (Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 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": + val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 1.25f; + break; + + case "pc": + val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 15; + break; + + case "mm": + val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 3.543307f; + break; + + case "cm": + val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 35.43307f; + break; + + case "in": + val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 90; + break; + + default: + val = Float.valueOf(length); } - return Float.valueOf(percentage) * scale + offset; + + return val * scale + offset; + } else { + return Float.valueOf(length) * scale + offset; } } } From 0e2b523f60def3e02c5d8bd9ea55350897e809ee Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 20 Jul 2017 02:07:50 +0300 Subject: [PATCH 054/198] Implement correct canvas width/height, font-size and percentage calculation and units. --- .../java/com/horcrux/svg/GlyphContext.java | 18 +++++++----- .../java/com/horcrux/svg/GroupShadowNode.java | 13 +++++---- .../main/java/com/horcrux/svg/PropHelper.java | 29 ++++++++++--------- .../java/com/horcrux/svg/TextShadowNode.java | 4 +-- .../java/com/horcrux/svg/VirtualNode.java | 26 +++++++++++++++-- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 68dfa8c5e..6eaa5bb48 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -220,6 +220,14 @@ private float getGlyphPosition(boolean isX) { return value; } + float getWidth() { + return mWidth; + } + + float getHeight() { + return mHeight; + } + double getFontSize() { for (int index = top; index >= 0; index--) { ReadableMap font = mFontContext.get(index); @@ -238,11 +246,10 @@ private float getGlyphPosition(boolean isX) { ReadableMap getGlyphFont() { float letterSpacing = DEFAULT_LETTER_SPACING; - float fontSize = DEFAULT_FONT_SIZE; + float fontSize = (float) getFontSize(); float kerning = DEFAULT_KERNING; boolean letterSpacingSet = false; - boolean fontSizeSet = false; boolean kerningSet = false; String fontFamily = null; @@ -257,11 +264,6 @@ ReadableMap getGlyphFont() { fontFamily = font.getString("fontFamily"); } - if (!fontSizeSet && font.hasKey("fontSize")) { - fontSize = (float) font.getDouble("fontSize"); - fontSizeSet = true; - } - if (!kerningSet && font.hasKey("kerning")) { kerning = Float.valueOf(font.getString("kerning")); kerningSet = true; @@ -280,7 +282,7 @@ ReadableMap getGlyphFont() { fontStyle = font.getString("fontStyle"); } - if (fontFamily != null && fontSizeSet && kerningSet && letterSpacingSet && fontWeight != null && fontStyle != null) { + if (fontFamily != null && kerningSet && letterSpacingSet && fontWeight != null && fontStyle != null) { break; } } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index ef24f8438..1ead9828c 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -15,6 +15,8 @@ import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; @@ -36,14 +38,13 @@ public void setFont(@Nullable ReadableMap font) { markUpdated(); } - void setupGlyphContext() { - mGlyphContext = new GlyphContext(mScale, getCanvasWidth(), getCanvasHeight()); + void setupGlyphContext(Canvas canvas) { + RectF clipBounds = new RectF(canvas.getClipBounds()); + mMatrix.mapRect(clipBounds); + mGlyphContext = new GlyphContext(mScale, clipBounds.width(), clipBounds.height()); } GlyphContext getGlyphContext() { - if (mGlyphContext == null) { - setupGlyphContext(); - } return mGlyphContext; } @@ -72,7 +73,7 @@ float getNextGlyphRotationFromContext() { } public void draw(final Canvas canvas, final Paint paint, final float opacity) { - setupGlyphContext(); + setupGlyphContext(canvas); if (opacity > MIN_OPACITY_FOR_DRAW) { clip(canvas, paint); drawGroup(canvas, paint, opacity); diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 5c08f5134..dffae1a6a 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -86,22 +86,25 @@ static int toFloatArray(ReadableArray value, float[] into) { static float fromRelativeToFloat(String length, float relative, float offset, float scale, double fontSize) { length = length.trim(); - int percentIndex = length.length() - 1; - if (length.codePointAt(percentIndex) == '%') { + int stringLength = length.length(); + int percentIndex = stringLength - 1; + if (stringLength == 0) { + return offset; + } else if (length.codePointAt(percentIndex) == '%') { return Float.valueOf(length.substring(0, percentIndex)) / 100 * relative + offset; } else { - int twoLetterUnitIndex = length.length() - 2; + int twoLetterUnitIndex = stringLength - 2; if (twoLetterUnitIndex > 0) { String lastTwo = length.substring(twoLetterUnitIndex); + int end = twoLetterUnitIndex; + float unit = 1; - float val; switch (lastTwo) { case "px": - val = Float.valueOf(length.substring(0, twoLetterUnitIndex)); break; case "em": - val = (float) (Float.valueOf(length.substring(0, twoLetterUnitIndex)) * fontSize); + unit = (float) fontSize; break; /* @@ -113,30 +116,30 @@ static float fromRelativeToFloat(String length, float relative, float offset, fl */ case "pt": - val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 1.25f; + unit = 1.25f; break; case "pc": - val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 15; + unit = 15; break; case "mm": - val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 3.543307f; + unit = 3.543307f; break; case "cm": - val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 35.43307f; + unit = 35.43307f; break; case "in": - val = Float.valueOf(length.substring(0, twoLetterUnitIndex)) * 90; + unit = 90; break; default: - val = Float.valueOf(length); + end = stringLength; } - return val * scale + offset; + return Float.valueOf(length.substring(0, end)) * unit * scale + offset; } else { return Float.valueOf(length) * scale + offset; } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index bfff1fba1..4302045a6 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -96,7 +96,7 @@ public void setFont(@Nullable ReadableMap font) { @Override public void draw(Canvas canvas, Paint paint, float opacity) { if (opacity > MIN_OPACITY_FOR_DRAW) { - setupGlyphContext(); + setupGlyphContext(canvas); clip(canvas, paint); getGroupPath(canvas, paint); drawGroup(canvas, paint, opacity); @@ -106,7 +106,7 @@ public void draw(Canvas canvas, Paint paint, float opacity) { @Override protected Path getPath(Canvas canvas, Paint paint) { - setupGlyphContext(); + setupGlyphContext(canvas); Path groupPath = getGroupPath(canvas, paint); releaseCachedPath(); return groupPath; diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 1a543ebab..f56354f4a 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -61,6 +61,8 @@ public abstract class VirtualNode extends LayoutShadowNode { private Path mCachedClipPath; private GroupShadowNode mParentTextRoot; private GroupShadowNode mTextRoot; + private float canvasHeight = -1; + private float canvasWidth = -1; public VirtualNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; @@ -344,11 +346,31 @@ protected float relativeOnOther(String length) { } protected float getCanvasWidth() { - return getSvgShadowNode().getCanvasBounds().width(); + if (canvasWidth != -1) { + return canvasWidth; + } + GroupShadowNode root = getTextRoot(); + if (root == null) { + canvasWidth = getSvgShadowNode().getCanvasBounds().width(); + } else { + canvasWidth = root.getGlyphContext().getWidth(); + } + + return canvasWidth; } protected float getCanvasHeight() { - return getSvgShadowNode().getCanvasBounds().height(); + if (canvasHeight != -1) { + return canvasHeight; + } + GroupShadowNode root = getTextRoot(); + if (root == null) { + canvasHeight = getSvgShadowNode().getCanvasBounds().height(); + } else { + canvasHeight = root.getGlyphContext().getHeight(); + } + + return canvasHeight; } protected float getCanvasLeft() { From 8ffa2f3373e2fd9c9227459733a28b2b4ea1ae59 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 20 Jul 2017 06:18:23 +0300 Subject: [PATCH 055/198] Implement correct ViewBox and Image transform --- .../java/com/horcrux/svg/ImageShadowNode.java | 12 +++++--- .../main/java/com/horcrux/svg/ViewBox.java | 30 +++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index 395edfc5a..f7529ec4d 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -213,16 +213,20 @@ private void doRender(Canvas canvas, Paint paint, Bitmap bitmap, float opacity) } RectF vbRect = new RectF(0, 0, renderRect.width() / mScale, renderRect.height() / mScale); - RectF eRect = new RectF(getCanvasLeft(), getCanvasTop(), rectWidth / mScale + getCanvasLeft(), rectHeight / mScale + getCanvasTop()); + float canvasLeft = getCanvasLeft(); + float canvasTop = getCanvasTop(); + RectF eRect = new RectF(canvasLeft, canvasTop, rectWidth / mScale + canvasLeft, rectHeight / mScale + canvasTop); Matrix transform = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice, false); - transform.mapRect(renderRect); Matrix translation = new Matrix(); - translation.postTranslate(rectX, rectY); if (mMatrix != null) { - translation.preConcat(mMatrix); + translation.postConcat(mMatrix); } + float dx = rectX + canvasLeft; + float dy = rectY + canvasTop; + translation.postTranslate(-dx, -dy); translation.mapRect(renderRect); + transform.mapRect(renderRect); Path clip = new Path(); diff --git a/android/src/main/java/com/horcrux/svg/ViewBox.java b/android/src/main/java/com/horcrux/svg/ViewBox.java index 508fc1edb..1b4446b54 100644 --- a/android/src/main/java/com/horcrux/svg/ViewBox.java +++ b/android/src/main/java/com/horcrux/svg/ViewBox.java @@ -43,10 +43,10 @@ static public Matrix getTransform(RectF vbRect, RectF eRect, String align, int m // Initialize scale-y to e-height/vb-height. float scaleY = eHeight / vbHeight; - // Initialize translate-x to vb-x - e-x. - // Initialize translate-y to vb-y - e-y. - float translateX = vbX - eX; - float translateY = vbY - eY; + // Initialize translate-x to e-x - (vb-x * scale-x). + // Initialize translate-y to e-y - (vb-y * scale-y). + float translateX = eX - (vbX * scaleX); + float translateY = eY - (vbY * scaleY); // If align is 'none' if (meetOrSlice == MOS_NONE) { @@ -65,7 +65,7 @@ static public Matrix getTransform(RectF vbRect, RectF eRect, String align, int m translateY -= (eHeight - vbHeight * scale) / 2; } } else { -// If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller. + // If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller. // Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the larger. if (!align.equals("none") && meetOrSlice == MOS_MEET) { @@ -74,24 +74,24 @@ static public Matrix getTransform(RectF vbRect, RectF eRect, String align, int m scaleX = scaleY = Math.max(scaleX, scaleY); } - // If align contains 'xMid', minus (e-width / scale-x - vb-width) / 2 from transform-x. + // If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x. if (align.contains("xMid")) { - translateX -= (eWidth / scaleX - vbWidth) / 2; + translateX += (eWidth - vbWidth * scaleX) / 2.0f; } - // If align contains 'xMax', minus (e-width / scale-x - vb-width) from transform-x. + // If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x. if (align.contains("xMax")) { - translateX -= eWidth / scaleX - vbWidth; + translateX += (eWidth - vbWidth * scaleX); } - // If align contains 'yMid', minus (e-height / scale-y - vb-height) / 2 from transform-y. + // If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y. if (align.contains("YMid")) { - translateY -= (eHeight / scaleY - vbHeight) / 2; + translateY += (eHeight - vbHeight * scaleY) / 2.0f; } - // If align contains 'yMax', minus (e-height / scale-y - vb-height) from transform-y. + // If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y. if (align.contains("YMax")) { - translateY -= eHeight / scaleY - vbHeight; + translateY += (eHeight - vbHeight * scaleY); } } @@ -99,8 +99,8 @@ static public Matrix getTransform(RectF vbRect, RectF eRect, String align, int m // The transform applied to content contained by the element is given by // translate(translate-x, translate-y) scale(scale-x, scale-y). Matrix transform = new Matrix(); - transform.postTranslate(-translateX * (fromSymbol ? scaleX : 1), -translateY * (fromSymbol ? scaleY : 1)); - transform.postScale(scaleX, scaleY); + transform.postTranslate(translateX * (fromSymbol ? scaleX : 1), translateY * (fromSymbol ? scaleY : 1)); + transform.preScale(scaleX, scaleY); return transform; } } From 4ab1baa182aeef28aea359518195bdc36dc147b0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 20 Jul 2017 18:37:27 +0300 Subject: [PATCH 056/198] Simplify pull request git diff --- .../java/com/horcrux/svg/TextShadowNode.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 4302045a6..9b026fd91 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -116,17 +116,17 @@ private int getTextAnchor() { return mTextAnchor; } - int getTextDecoration() { - int decoration = mTextDecoration; - if (decoration != TEXT_DECORATION_NONE) { - return decoration; + int getComputedTextAnchor() { + int anchor = mTextAnchor; + if (anchor != TEXT_ANCHOR_AUTO) { + return anchor; } ReactShadowNode shadowNode = this.getParent(); while (shadowNode instanceof GroupShadowNode) { if (shadowNode instanceof TextShadowNode) { - decoration = ((TextShadowNode) shadowNode).getTextDecoration(); - if (decoration != TEXT_DECORATION_NONE) { + anchor = ((TextShadowNode) shadowNode).getTextAnchor(); + if (anchor != TEXT_ANCHOR_AUTO) { break; } } @@ -134,20 +134,20 @@ int getTextDecoration() { shadowNode = shadowNode.getParent(); } - return decoration; + return anchor; } - int getComputedTextAnchor() { - int anchor = mTextAnchor; - if (anchor != TEXT_ANCHOR_AUTO) { - return anchor; + int getTextDecoration() { + int decoration = mTextDecoration; + if (decoration != TEXT_DECORATION_NONE) { + return decoration; } ReactShadowNode shadowNode = this.getParent(); while (shadowNode instanceof GroupShadowNode) { if (shadowNode instanceof TextShadowNode) { - anchor = ((TextShadowNode) shadowNode).getTextAnchor(); - if (anchor != TEXT_ANCHOR_AUTO) { + decoration = ((TextShadowNode) shadowNode).getTextDecoration(); + if (decoration != TEXT_DECORATION_NONE) { break; } } @@ -155,7 +155,7 @@ int getComputedTextAnchor() { shadowNode = shadowNode.getParent(); } - return anchor; + return decoration; } protected void releaseCachedPath() { From 48b1d0882c79ba5214ee44722d24a4ae6ddce260 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 20 Jul 2017 21:50:48 +0300 Subject: [PATCH 057/198] ViewBox, Text, Image, Gradient and Path rendering and generic matrix transforms now conforms with chrome, firefox, opera, and, edge; on desktop windows 10. And, on mobile chrome and firefox on android. Fix resetting of location, delta, and, rotation; on every opening Text tag. Stock Browser in Android Emulator doesn't support text on a path, requires setting precomputed x, y, dx, dy, and, rotate attributes for each character/glyph. --- .../main/java/com/horcrux/svg/GlyphContext.java | 14 +++++++++++--- .../java/com/horcrux/svg/TextPathShadowNode.java | 1 - .../main/java/com/horcrux/svg/TextShadowNode.java | 13 +++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 6eaa5bb48..edb81b928 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -69,7 +69,11 @@ void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { top++; } - void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY) { + void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY, boolean resetLocation) { + if (resetLocation) { + reset(); + } + if (positionX != null) { mXs = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); } @@ -106,6 +110,11 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read top++; } + private void reset() { + mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; + mCurrentLocation.x = mCurrentLocation.y = 0; + } + void popContext() { mContextLength--; top--; @@ -130,8 +139,7 @@ void popContext() { mXs = mXPositionsContext.get(top); mYs = mYPositionsContext.get(top); } else { - mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; - mCurrentLocation.x = mCurrentLocation.y = 0; + reset(); } } diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index 9e07d9b02..aba12ee90 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -95,5 +95,4 @@ protected void pushGlyphContext() { protected void popGlyphContext() { // do nothing } - } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 9b026fd91..7f2f05d4b 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -39,11 +39,11 @@ class TextShadowNode extends GroupShadowNode { private int mTextAnchor = TEXT_ANCHOR_AUTO; private int mTextDecoration = TEXT_DECORATION_NONE; - private @Nullable ReadableArray mRotate; - private @Nullable ReadableArray mDeltaX; - private @Nullable ReadableArray mDeltaY; - private @Nullable String mPositionX; - private @Nullable String mPositionY; + @Nullable ReadableArray mRotate; + @Nullable ReadableArray mDeltaX; + @Nullable ReadableArray mDeltaY; + @Nullable String mPositionX; + @Nullable String mPositionY; @ReactProp(name = "textAnchor", defaultInt = TEXT_ANCHOR_AUTO) public void setTextAnchor(int textAnchor) { @@ -178,6 +178,7 @@ Path getGroupPath(Canvas canvas, Paint paint) { @Override protected void pushGlyphContext() { - getTextRoot().getGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY); + boolean isTextNode = !(this instanceof TextPathShadowNode) && !(this instanceof TSpanShadowNode); + getTextRoot().getGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY, isTextNode); } } From 3960a11c212103dbb23a1072aa1f3e7f80aab0da Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 02:41:54 +0300 Subject: [PATCH 058/198] Fix code inspection / analysis warnings. Update sdk, build tools, gradle, and target sdk version. --- android/build.gradle | 8 +-- .../src/main/java/com/horcrux/svg/Brush.java | 27 +++---- .../com/horcrux/svg/CircleShadowNode.java | 4 +- .../com/horcrux/svg/ClipPathShadowNode.java | 4 +- .../com/horcrux/svg/DefinitionShadowNode.java | 6 +- .../java/com/horcrux/svg/DefsShadowNode.java | 10 ++- .../com/horcrux/svg/EllipseShadowNode.java | 4 +- .../java/com/horcrux/svg/GlyphContext.java | 30 ++++---- .../java/com/horcrux/svg/GroupShadowNode.java | 19 ++--- .../java/com/horcrux/svg/ImageShadowNode.java | 12 ++-- .../java/com/horcrux/svg/LineShadowNode.java | 4 +- .../horcrux/svg/LinearGradientShadowNode.java | 8 +-- .../java/com/horcrux/svg/PathShadowNode.java | 4 +- .../main/java/com/horcrux/svg/PropHelper.java | 48 ++++++------- .../horcrux/svg/RadialGradientShadowNode.java | 8 +-- .../java/com/horcrux/svg/RectShadowNode.java | 4 +- .../com/horcrux/svg/RenderableShadowNode.java | 15 ++-- .../horcrux/svg/RenderableViewManager.java | 38 +++++----- .../main/java/com/horcrux/svg/SvgPackage.java | 2 +- .../main/java/com/horcrux/svg/SvgView.java | 6 +- .../java/com/horcrux/svg/SvgViewManager.java | 4 +- .../java/com/horcrux/svg/SvgViewModule.java | 8 +-- .../com/horcrux/svg/SvgViewShadowNode.java | 30 ++++---- .../com/horcrux/svg/SymbolShadowNode.java | 8 +-- .../com/horcrux/svg/TextPathShadowNode.java | 6 +- .../java/com/horcrux/svg/TextShadowNode.java | 17 +++-- .../java/com/horcrux/svg/UseShadowNode.java | 4 +- .../main/java/com/horcrux/svg/ViewBox.java | 8 +-- .../java/com/horcrux/svg/VirtualNode.java | 72 +++++++++---------- 29 files changed, 202 insertions(+), 216 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index aa1c35f74..f1ecfd90f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,19 +4,19 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' } } apply plugin: 'com.android.library' android { - compileSdkVersion 25 - buildToolsVersion "25.0.1" + compileSdkVersion 26 + buildToolsVersion "25.0.3" defaultConfig { minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 26 versionCode 1 versionName "1.0" } diff --git a/android/src/main/java/com/horcrux/svg/Brush.java b/android/src/main/java/com/horcrux/svg/Brush.java index 9f3b1eadf..b641b3f75 100644 --- a/android/src/main/java/com/horcrux/svg/Brush.java +++ b/android/src/main/java/com/horcrux/svg/Brush.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -20,21 +20,21 @@ import com.facebook.react.bridge.ReadableArray; -public class Brush { +class Brush { private BrushType mType = BrushType.LINEAR_GRADIENT; - private ReadableArray mPoints; + private final ReadableArray mPoints; private ReadableArray mColors; private Matrix mMatrix; - private boolean mUseObjectBoundingBox; + private final boolean mUseObjectBoundingBox; private Rect mUserSpaceBoundingBox; - public Brush(BrushType type, ReadableArray points, BrushUnits units) { + Brush(BrushType type, ReadableArray points, BrushUnits units) { mType = type; mPoints = points; mUseObjectBoundingBox = units == BrushUnits.OBJECT_BOUNDING_BOX; } - public enum BrushType { + enum BrushType { LINEAR_GRADIENT(0), RADIAL_GRADIENT(1), PATTERN(2); @@ -45,7 +45,7 @@ public enum BrushType { final int nativeInt; } - public enum BrushUnits { + enum BrushUnits { OBJECT_BOUNDING_BOX(0), USER_SPACE_ON_USE(1); BrushUnits(int ni) { @@ -67,15 +67,15 @@ private static void parseGradientStops(ReadableArray value, int stopsCount, floa } } - public void setUserSpaceBoundingBox(Rect userSpaceBoundingBox) { + void setUserSpaceBoundingBox(Rect userSpaceBoundingBox) { mUserSpaceBoundingBox = userSpaceBoundingBox; } - public void setGradientColors(ReadableArray colors) { + void setGradientColors(ReadableArray colors) { mColors = colors; } - public void setGradientTransform(Matrix matrix) { + void setGradientTransform(Matrix matrix) { mMatrix = matrix; } @@ -94,7 +94,7 @@ private RectF getPaintRect(RectF pathBoundingBox) { return new RectF(x, y, x + width, y + height); } - public void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity) { + void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity) { RectF rect = getPaintRect(pathBoundingBox); float width = rect.width(); float height = rect.height(); @@ -154,12 +154,13 @@ public void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float op radialGradient.setLocalMatrix(radialMatrix); paint.setShader(radialGradient); - } else { + } + // else { // todo: pattern support //Shader mShader1 = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); //paint.setShader(mShader1); //bitmap.recycle(); - } + // } } } diff --git a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java index 509194357..5d917875c 100644 --- a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -18,7 +18,7 @@ /** * Shadow node for virtual Circle view */ -public class CircleShadowNode extends RenderableShadowNode { +class CircleShadowNode extends RenderableShadowNode { private String mCx; private String mCy; diff --git a/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java b/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java index bc8478644..42c11f5cd 100644 --- a/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -20,7 +20,7 @@ /** * Shadow node for virtual ClipPath view */ -public class ClipPathShadowNode extends GroupShadowNode { +class ClipPathShadowNode extends GroupShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { diff --git a/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java b/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java index 197aebe73..bb51c74cd 100644 --- a/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -15,12 +15,10 @@ import android.graphics.Path; import android.graphics.Point; -import com.facebook.react.bridge.ReadableArray; - /** * Shadow node for virtual Definition type views */ -public class DefinitionShadowNode extends VirtualNode { +class DefinitionShadowNode extends VirtualNode { public void draw(Canvas canvas, Paint paint, float opacity) {} diff --git a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java index 88f906c7c..dbbb272b5 100644 --- a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -15,21 +15,19 @@ /** * Shadow node for virtual Defs view */ -public class DefsShadowNode extends DefinitionShadowNode { +class DefsShadowNode extends DefinitionShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { traverseChildren(new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { node.saveDefinition(); - return true; } }); NodeRunnable markUpdateSeenRecursive = new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { node.markUpdateSeen(); node.traverseChildren(this); - return true; } }; traverseChildren(markUpdateSeenRecursive); diff --git a/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java b/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java index 3c013ce6a..a6a982e0d 100644 --- a/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -19,7 +19,7 @@ /** * Shadow node for virtual Ellipse view */ -public class EllipseShadowNode extends RenderableShadowNode { +class EllipseShadowNode extends RenderableShadowNode { private String mCx; private String mCy; diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index edb81b928..ea8f147a5 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -28,26 +28,26 @@ class GlyphContext { private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_LETTER_SPACING = 0f; - private ArrayList> mXPositionsContext = new ArrayList<>(); - private ArrayList> mYPositionsContext = new ArrayList<>(); - private ArrayList> mRotationsContext = new ArrayList<>(); - private ArrayList> mDeltaXsContext = new ArrayList<>(); - private ArrayList> mDeltaYsContext = new ArrayList<>(); - private ArrayList mFontContext = new ArrayList<>(); - private ArrayList mRotationContext = new ArrayList<>(); - private ArrayList mNodes = new ArrayList<>(); + private final ArrayList> mXPositionsContext = new ArrayList<>(); + private final ArrayList> mYPositionsContext = new ArrayList<>(); + private final ArrayList> mRotationsContext = new ArrayList<>(); + private final ArrayList> mDeltaXsContext = new ArrayList<>(); + private final ArrayList> mDeltaYsContext = new ArrayList<>(); + private final ArrayList mFontContext = new ArrayList<>(); + private final ArrayList mRotationContext = new ArrayList<>(); + private final ArrayList mNodes = new ArrayList<>(); private ArrayList mRotations = new ArrayList<>(); - private @Nonnull PointF mCurrentLocation = new PointF(); + private @Nonnull final PointF mCurrentLocation = new PointF(); private ArrayList mDeltaXs = new ArrayList<>(); private ArrayList mDeltaYs = new ArrayList<>(); - private @Nonnull PointF mCurrentDelta = new PointF(); + private @Nonnull final PointF mCurrentDelta = new PointF(); private ArrayList mXs = new ArrayList<>(); private ArrayList mYs = new ArrayList<>(); private int mContextLength; private float mRotation; - private float mHeight; - private float mWidth; - private float mScale; + private final float mHeight; + private final float mWidth; + private final float mScale; private int top = -1; GlyphContext(float scale, float width, float height) { @@ -158,9 +158,8 @@ PointF getNextGlyphDelta() { float getNextGlyphRotation() { List prev = null; - int index = top; - for (; index >= 0; index--) { + for (int index = top; index >= 0; index--) { List rotations = mRotationsContext.get(index); if (prev != rotations && rotations.size() != 0) { @@ -264,7 +263,6 @@ ReadableMap getGlyphFont() { String fontWeight = null; String fontStyle = null; - // TODO: add support for other length units for (int index = top; index >= 0; index--) { ReadableMap font = mFontContext.get(index); diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 1ead9828c..ab200b817 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -15,7 +15,6 @@ import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; -import android.graphics.Rect; import android.graphics.RectF; import com.facebook.react.bridge.ReadableMap; @@ -48,11 +47,11 @@ GlyphContext getGlyphContext() { return mGlyphContext; } - protected void pushGlyphContext() { + void pushGlyphContext() { getTextRoot().getGlyphContext().pushContext(this, mFont); } - protected void popGlyphContext() { + void popGlyphContext() { getTextRoot().getGlyphContext().popContext(); } @@ -85,7 +84,7 @@ void drawGroup(final Canvas canvas, final Paint paint, final float opacity) { final SvgViewShadowNode svg = getSvgShadowNode(); final GroupShadowNode self = this; traverseChildren(new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { if (node instanceof RenderableShadowNode) { ((RenderableShadowNode)node).mergeProperties(self); } @@ -103,7 +102,6 @@ public boolean run(VirtualNode node) { if (node.isResponsible()) { svg.enableTouchEvents(); } - return true; } }); popGlyphContext(); @@ -118,9 +116,8 @@ protected Path getPath(final Canvas canvas, final Paint paint) { final Path path = new Path(); traverseChildren(new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { path.addPath(node.getPath(canvas, paint)); - return true; } }); @@ -163,15 +160,14 @@ public int hitTest(final Point point, final @Nullable Matrix matrix) { return -1; } - protected void saveDefinition() { + void saveDefinition() { if (mName != null) { getSvgShadowNode().defineTemplate(this, mName); } traverseChildren(new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { node.saveDefinition(); - return true; } }); } @@ -179,11 +175,10 @@ public boolean run(VirtualNode node) { @Override public void resetProperties() { traverseChildren(new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { if (node instanceof RenderableShadowNode) { ((RenderableShadowNode)node).resetProperties(); } - return true; } }); } diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index f7529ec4d..e5ff63f85 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -43,7 +43,7 @@ /** * Shadow node for virtual Image view */ -public class ImageShadowNode extends RenderableShadowNode { +class ImageShadowNode extends RenderableShadowNode { private String mX; private String mY; @@ -53,11 +53,11 @@ public class ImageShadowNode extends RenderableShadowNode { private float mImageRatio; private String mAlign; private int mMeetOrSlice; - private AtomicBoolean mLoading = new AtomicBoolean(false); + private final AtomicBoolean mLoading = new AtomicBoolean(false); private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; - protected Matrix mMatrix = new Matrix(); + private Matrix mMatrix = new Matrix(); @ReactProp(name = "x") public void setX(String x) { @@ -118,7 +118,7 @@ public void setMeetOrSlice(int meetOrSlice) { @ReactProp(name = "matrix") public void setMatrix(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); if (matrixSize == 6) { sRawMatrix[0] = sMatrixData[0]; sRawMatrix[1] = sMatrixData[2]; @@ -216,7 +216,7 @@ private void doRender(Canvas canvas, Paint paint, Bitmap bitmap, float opacity) float canvasLeft = getCanvasLeft(); float canvasTop = getCanvasTop(); RectF eRect = new RectF(canvasLeft, canvasTop, rectWidth / mScale + canvasLeft, rectHeight / mScale + canvasTop); - Matrix transform = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice, false); + Matrix transform = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); Matrix translation = new Matrix(); if (mMatrix != null) { diff --git a/android/src/main/java/com/horcrux/svg/LineShadowNode.java b/android/src/main/java/com/horcrux/svg/LineShadowNode.java index 7e17f22f5..8d134de6b 100644 --- a/android/src/main/java/com/horcrux/svg/LineShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LineShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -17,7 +17,7 @@ /** * Shadow node for virtual Line view */ -public class LineShadowNode extends RenderableShadowNode { +class LineShadowNode extends RenderableShadowNode { private String mX1; private String mY1; diff --git a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java index 5988b1697..0513353a9 100644 --- a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -23,7 +23,7 @@ /** * Shadow node for virtual LinearGradient definition view */ -public class LinearGradientShadowNode extends DefinitionShadowNode { +class LinearGradientShadowNode extends DefinitionShadowNode { private String mX1; private String mY1; @@ -34,7 +34,7 @@ public class LinearGradientShadowNode extends DefinitionShadowNode { private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; - protected Matrix mMatrix = new Matrix(); + private Matrix mMatrix = new Matrix(); @ReactProp(name = "x1") public void setX1(String x1) { @@ -82,7 +82,7 @@ public void setGradientUnits(int gradientUnits) { @ReactProp(name = "gradientTransform") public void setGradientTransform(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); if (matrixSize == 6) { sRawMatrix[0] = sMatrixData[0]; sRawMatrix[1] = sMatrixData[2]; diff --git a/android/src/main/java/com/horcrux/svg/PathShadowNode.java b/android/src/main/java/com/horcrux/svg/PathShadowNode.java index 15d4d0e33..17d3de880 100644 --- a/android/src/main/java/com/horcrux/svg/PathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/PathShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -19,7 +19,7 @@ /** * Shadow node for virtual Path view */ -public class PathShadowNode extends RenderableShadowNode { +class PathShadowNode extends RenderableShadowNode { private Path mPath; private PropHelper.PathParser mD; diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index dffae1a6a..58535c4b2 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -9,16 +9,8 @@ package com.horcrux.svg; -import android.graphics.Color; import android.graphics.Path; import android.graphics.RectF; -import android.graphics.Paint; -import android.graphics.RadialGradient; -import android.graphics.LinearGradient; -import android.graphics.Shader; -import android.graphics.Matrix; - -import javax.annotation.Nullable; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableArray; @@ -28,6 +20,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; + /** * Contains static helper methods for accessing props. */ @@ -44,13 +38,18 @@ class PropHelper { @Nullable float[] toFloatArray(@Nullable ReadableArray value) { if (value != null) { - float[] result = new float[value.size()]; - toFloatArray(value, result); - return result; + 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 transformInputMatrixSize = 6; + /** * Converts given {@link ReadableArray} to an array of {@code float}. Writes result to the array * passed in {@param into}. This method will write to the output array up to the number of items @@ -61,17 +60,18 @@ float[] toFloatArray(@Nullable ReadableArray value) { * @param into output array * @return number of items copied from input to the output array */ - - static int toFloatArray(ReadableArray value, float[] into) { - int length = value.size() > into.length ? into.length : value.size(); - for (int i = 0; i < length; i++) { + static int toMatrixData(ReadableArray value, float[] into) { + int fromSize = value.size(); + if (fromSize != transformInputMatrixSize) { + return fromSize; + } + for (int i = 0; i < transformInputMatrixSize; i++) { into[i] = (float) value.getDouble(i); } - return value.size(); - + return transformInputMatrixSize; } - static private Pattern percentageRegExp = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$"); + static private final Pattern percentageRegExp = Pattern.compile("^(-?\\d+(?:\\.\\d+)?)%$"); /** * Converts length string into actual based on a relative number @@ -158,12 +158,12 @@ static boolean isPercentage(String string) { } static class PathParser { - static private Pattern PATH_REG_EXP = Pattern.compile("[a-df-z]|[\\-+]?(?:[\\d.]e[\\-+]?|[^\\s\\-+,a-z])+", Pattern.CASE_INSENSITIVE); - static private Pattern DECIMAL_REG_EXP = Pattern.compile("(\\.\\d+)(?=\\-?\\.)"); + static private final Pattern PATH_REG_EXP = Pattern.compile("[a-df-z]|[\\-+]?(?:[\\d.]e[\\-+]?|[^\\s\\-+,a-z])+", Pattern.CASE_INSENSITIVE); + static private final Pattern DECIMAL_REG_EXP = Pattern.compile("(\\.\\d+)(?=-?\\.)"); private Matcher mMatcher; private Path mPath; - private String mString; + private final String mString; private float mPenX = 0f; private float mPenY = 0f; private float mPenDownX; @@ -179,12 +179,12 @@ static class PathParser { private WritableArray mBezierCurves; private WritableMap mLastStartPoint; - public PathParser(String d, float scale) { + PathParser(String d, float scale) { mScale = scale; mString = d; } - public ReadableArray getBezierCurves() { + ReadableArray getBezierCurves() { if (mBezierCurves == null) { getPath(); } diff --git a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java index 44f3c8f9f..96d789a46 100644 --- a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -23,7 +23,7 @@ /** * Shadow node for virtual RadialGradient definition view */ -public class RadialGradientShadowNode extends DefinitionShadowNode { +class RadialGradientShadowNode extends DefinitionShadowNode { private String mFx; private String mFy; private String mRx; @@ -35,7 +35,7 @@ public class RadialGradientShadowNode extends DefinitionShadowNode { private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; - protected Matrix mMatrix = new Matrix(); + private Matrix mMatrix = new Matrix(); @ReactProp(name = "fx") public void setFx(String fx) { @@ -95,7 +95,7 @@ public void setGradientUnits(int gradientUnits) { @ReactProp(name = "gradientTransform") public void setGradientTransform(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); if (matrixSize == 6) { sRawMatrix[0] = sMatrixData[0]; sRawMatrix[1] = sMatrixData[2]; diff --git a/android/src/main/java/com/horcrux/svg/RectShadowNode.java b/android/src/main/java/com/horcrux/svg/RectShadowNode.java index d44f90869..5f75c7455 100644 --- a/android/src/main/java/com/horcrux/svg/RectShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RectShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -18,7 +18,7 @@ /** * Shadow node for virtual Rect view */ -public class RectShadowNode extends RenderableShadowNode { +class RectShadowNode extends RenderableShadowNode { private String mX; private String mY; diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 0e3ed086f..c6b4f0e10 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -35,6 +35,7 @@ /** * Renderable shadow node */ +@SuppressWarnings("WeakerAccess") abstract public class RenderableShadowNode extends VirtualNode { // strokeLinecap @@ -218,7 +219,7 @@ public void draw(Canvas canvas, Paint paint, float opacity) { * Sets up paint according to the props set on a shadow view. Returns {@code true} * if the fill should be drawn, {@code false} if not. */ - protected boolean setupFillPaint(Paint paint, float opacity) { + private boolean setupFillPaint(Paint paint, float opacity) { if (mFill != null && mFill.size() > 0) { paint.reset(); paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG); @@ -233,7 +234,7 @@ protected boolean setupFillPaint(Paint paint, float opacity) { * Sets up paint according to the props set on a shadow view. Returns {@code true} * if the stroke should be drawn, {@code false} if not. */ - protected boolean setupStrokePaint(Paint paint, float opacity) { + private boolean setupStrokePaint(Paint paint, float opacity) { paint.reset(); float strokeWidth = relativeOnOther(mStrokeWidth); if (strokeWidth == 0 || mStroke == null || mStroke.size() == 0) { @@ -304,7 +305,7 @@ public int hitTest(Point point, @Nullable Matrix matrix) { } } - protected boolean pathContainsPoint(Path path, Matrix matrix, Point point) { + boolean pathContainsPoint(Path path, Matrix matrix, Point point) { Path copy = new Path(path); copy.transform(matrix); @@ -317,11 +318,11 @@ protected boolean pathContainsPoint(Path path, Matrix matrix, Point point) { return region.contains(point.x, point.y); } - public WritableArray getAttributeList() { + private WritableArray getAttributeList() { return mAttributeList; } - public void mergeProperties(RenderableShadowNode target) { + void mergeProperties(RenderableShadowNode target) { WritableArray targetAttributeList = target.getAttributeList(); if (targetAttributeList == null || @@ -351,7 +352,7 @@ public void mergeProperties(RenderableShadowNode target) { mLastMergedList = targetAttributeList; } - public void resetProperties() { + void resetProperties() { if (mLastMergedList != null && mOriginProperties != null) { try { for (int i = mLastMergedList.size() - 1; i >= 0; i--) { diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 6a80ea0b7..8805f7eed 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -20,24 +20,24 @@ * into native views and don't need any logic (all the logic is in {@link SvgView}), this * "stubbed" ViewManager is used for all of them. */ -public class RenderableViewManager extends ViewManager { - - /* package */ static final String CLASS_GROUP = "RNSVGGroup"; - /* package */ static final String CLASS_PATH = "RNSVGPath"; - /* package */ static final String CLASS_TEXT = "RNSVGText"; - /* package */ static final String CLASS_TSPAN = "RNSVGTSpan"; - /* package */ static final String CLASS_TEXT_PATH = "RNSVGTextPath"; - /* package */ static final String CLASS_IMAGE = "RNSVGImage"; - /* package */ static final String CLASS_CIRCLE = "RNSVGCircle"; - /* package */ static final String CLASS_ELLIPSE = "RNSVGEllipse"; - /* package */ static final String CLASS_LINE = "RNSVGLine"; - /* package */ static final String CLASS_RECT = "RNSVGRect"; - /* package */ static final String CLASS_CLIP_PATH = "RNSVGClipPath"; - /* package */ static final String CLASS_DEFS = "RNSVGDefs"; - /* package */ static final String CLASS_USE = "RNSVGUse"; - /* package */ static final String CLASS_SYMBOL = "RNSVGSymbol"; - /* package */ static final String CLASS_LINEAR_GRADIENT = "RNSVGLinearGradient"; - /* package */ static final String CLASS_RADIAL_GRADIENT = "RNSVGRadialGradient"; +class RenderableViewManager extends ViewManager { + + /* package */ private static final String CLASS_GROUP = "RNSVGGroup"; + /* package */ private static final String CLASS_PATH = "RNSVGPath"; + /* package */ private static final String CLASS_TEXT = "RNSVGText"; + /* package */ private static final String CLASS_TSPAN = "RNSVGTSpan"; + /* package */ private static final String CLASS_TEXT_PATH = "RNSVGTextPath"; + /* package */ private static final String CLASS_IMAGE = "RNSVGImage"; + /* package */ private static final String CLASS_CIRCLE = "RNSVGCircle"; + /* package */ private static final String CLASS_ELLIPSE = "RNSVGEllipse"; + /* package */ private static final String CLASS_LINE = "RNSVGLine"; + /* package */ private static final String CLASS_RECT = "RNSVGRect"; + /* package */ private static final String CLASS_CLIP_PATH = "RNSVGClipPath"; + /* package */ private static final String CLASS_DEFS = "RNSVGDefs"; + /* package */ private static final String CLASS_USE = "RNSVGUse"; + /* package */ private static final String CLASS_SYMBOL = "RNSVGSymbol"; + /* package */ private static final String CLASS_LINEAR_GRADIENT = "RNSVGLinearGradient"; + /* package */ private static final String CLASS_RADIAL_GRADIENT = "RNSVGRadialGradient"; private final String mClassName; diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 716a8da3c..d835c870c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index f2ccb1b3f..1ff967f45 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -46,7 +46,7 @@ public String toString() { } private @Nullable Bitmap mBitmap; - private EventDispatcher mEventDispatcher; + private final EventDispatcher mEventDispatcher; private long mGestureStartTime = TouchEvent.UNSET; private int mTargetTag; @@ -131,7 +131,7 @@ private void dispatch(MotionEvent ev, TouchEventType type) { mTouchEventCoalescingKeyHelper)); } - public void handleTouchEvent(MotionEvent ev) { + private void handleTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_DOWN) { mGestureStartTime = ev.getEventTime(); diff --git a/android/src/main/java/com/horcrux/svg/SvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java index 633bf5966..770b18e61 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -24,7 +24,7 @@ * ViewManager for RNSVGSvgView React views. Renders as a {@link SvgView} and handles * invalidating the native view on shadow view updates happening in the underlying tree. */ -public class SvgViewManager extends BaseViewManager { +class SvgViewManager extends BaseViewManager { private static final String REACT_CLASS = "RNSVGSvgView"; diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java index 9c6fff625..b06f8c90c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -9,15 +9,13 @@ package com.horcrux.svg; -import android.util.Log; - import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -public class SvgViewModule extends ReactContextBaseJavaModule { - public SvgViewModule(ReactApplicationContext reactContext) { +class SvgViewModule extends ReactContextBaseJavaModule { + SvgViewModule(ReactApplicationContext reactContext) { super(reactContext); } diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index e76c6cae3..af4f8944c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -39,7 +39,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { private final Map mDefinedTemplates = new HashMap<>(); private final Map mDefinedBrushes = new HashMap<>(); private Canvas mCanvas; - protected final float mScale; + private final float mScale; private float mMinX; private float mMinY; @@ -111,7 +111,7 @@ public void setReactTag(int reactTag) { SvgViewManager.setShadowNode(this); } - public Object drawOutput() { + private Object drawOutput() { Bitmap bitmap = Bitmap.createBitmap( (int) getLayoutWidth(), (int) getLayoutHeight(), @@ -122,7 +122,7 @@ public Object drawOutput() { return bitmap; } - public Rect getCanvasBounds() { + Rect getCanvasBounds() { return mCanvas.getClipBounds(); } @@ -131,7 +131,7 @@ private void drawChildren(Canvas canvas) { if (mAlign != null) { RectF vbRect = getViewBox(); RectF eRect = new RectF(0, 0, getLayoutWidth(), getLayoutHeight()); - mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice, false); + mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); canvas.concat(mViewBoxMatrix); } @@ -161,11 +161,11 @@ private void drawChildren(Canvas canvas) { } @NonNull - RectF getViewBox() { + private RectF getViewBox() { return new RectF(mMinX * mScale, mMinY * mScale, (mMinX + mVbWidth) * mScale, (mMinY + mVbHeight) * mScale); } - public String toDataURL() { + String toDataURL() { Bitmap bitmap = Bitmap.createBitmap( (int) getLayoutWidth(), (int) getLayoutHeight(), @@ -179,13 +179,13 @@ public String toDataURL() { return Base64.encodeToString(bitmapBytes, Base64.DEFAULT); } - public void enableTouchEvents() { + void enableTouchEvents() { if (!mResponsible) { mResponsible = true; } } - public int hitTest(Point point) { + int hitTest(Point point) { if (!mResponsible) { return -1; } @@ -206,27 +206,27 @@ public int hitTest(Point point) { return viewTag; } - public void defineClipPath(VirtualNode clipPath, String clipPathRef) { + void defineClipPath(VirtualNode clipPath, String clipPathRef) { mDefinedClipPaths.put(clipPathRef, clipPath); } - public VirtualNode getDefinedClipPath(String clipPathRef) { + VirtualNode getDefinedClipPath(String clipPathRef) { return mDefinedClipPaths.get(clipPathRef); } - public void defineTemplate(VirtualNode template, String templateRef) { + void defineTemplate(VirtualNode template, String templateRef) { mDefinedTemplates.put(templateRef, template); } - public VirtualNode getDefinedTemplate(String templateRef) { + VirtualNode getDefinedTemplate(String templateRef) { return mDefinedTemplates.get(templateRef); } - public void defineBrush(Brush brush, String brushRef) { + void defineBrush(Brush brush, String brushRef) { mDefinedBrushes.put(brushRef, brush); } - public Brush getDefinedBrush(String brushRef) { + Brush getDefinedBrush(String brushRef) { return mDefinedBrushes.get(brushRef); } } diff --git a/android/src/main/java/com/horcrux/svg/SymbolShadowNode.java b/android/src/main/java/com/horcrux/svg/SymbolShadowNode.java index e41e199ca..6fb647488 100644 --- a/android/src/main/java/com/horcrux/svg/SymbolShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SymbolShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -16,7 +16,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; -public class SymbolShadowNode extends GroupShadowNode { +class SymbolShadowNode extends GroupShadowNode { private float mMinX; private float mMinY; @@ -66,11 +66,11 @@ public void draw(Canvas canvas, Paint paint, float opacity) { saveDefinition(); } - public void drawSymbol(Canvas canvas, Paint paint, float opacity, float width, float height) { + void drawSymbol(Canvas canvas, Paint paint, float opacity, float width, float height) { if (mAlign != null) { RectF vbRect = new RectF(mMinX * mScale, mMinY * mScale, (mMinX + mVbWidth) * mScale, (mMinY + mVbHeight) * mScale); RectF eRect = new RectF(0, 0, width, height); - Matrix viewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice, false); + Matrix viewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); canvas.concat(viewBoxMatrix); super.draw(canvas, paint, opacity); } diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index aba12ee90..36e043d2c 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -20,7 +20,7 @@ /** * Shadow node for virtual TextPath view */ -public class TextPathShadowNode extends TextShadowNode { +class TextPathShadowNode extends TextShadowNode { private String mHref; private String mMethod; @@ -51,7 +51,7 @@ public void setSpacing(@Nullable String spacing) { markUpdated(); } - public String getMethod() { + String getMethod() { return mMethod; } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 7f2f05d4b..170a781a9 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -39,11 +39,11 @@ class TextShadowNode extends GroupShadowNode { private int mTextAnchor = TEXT_ANCHOR_AUTO; private int mTextDecoration = TEXT_DECORATION_NONE; - @Nullable ReadableArray mRotate; - @Nullable ReadableArray mDeltaX; - @Nullable ReadableArray mDeltaY; - @Nullable String mPositionX; - @Nullable String mPositionY; + private @Nullable ReadableArray mRotate; + private @Nullable ReadableArray mDeltaX; + private @Nullable ReadableArray mDeltaY; + private @Nullable String mPositionX; + private @Nullable String mPositionY; @ReactProp(name = "textAnchor", defaultInt = TEXT_ANCHOR_AUTO) public void setTextAnchor(int textAnchor) { @@ -158,12 +158,11 @@ int getTextDecoration() { return decoration; } - protected void releaseCachedPath() { + void releaseCachedPath() { traverseChildren(new NodeRunnable() { - public boolean run(VirtualNode node) { + public void run(VirtualNode node) { TextShadowNode text = (TextShadowNode)node; text.releaseCachedPath(); - return true; } }); } @@ -177,7 +176,7 @@ Path getGroupPath(Canvas canvas, Paint paint) { } @Override - protected void pushGlyphContext() { + void pushGlyphContext() { boolean isTextNode = !(this instanceof TextPathShadowNode) && !(this instanceof TSpanShadowNode); getTextRoot().getGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY, isTextNode); } diff --git a/android/src/main/java/com/horcrux/svg/UseShadowNode.java b/android/src/main/java/com/horcrux/svg/UseShadowNode.java index cb29bd71f..b9bbf62bc 100644 --- a/android/src/main/java/com/horcrux/svg/UseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/UseShadowNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -20,7 +20,7 @@ /** * Shadow node for virtual Use view */ -public class UseShadowNode extends RenderableShadowNode { +class UseShadowNode extends RenderableShadowNode { private String mHref; private String mWidth; diff --git a/android/src/main/java/com/horcrux/svg/ViewBox.java b/android/src/main/java/com/horcrux/svg/ViewBox.java index 1b4446b54..5660a1c0a 100644 --- a/android/src/main/java/com/horcrux/svg/ViewBox.java +++ b/android/src/main/java/com/horcrux/svg/ViewBox.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -15,13 +15,13 @@ /** * Shadow node for virtual ViewBox */ -public class ViewBox extends GroupShadowNode { +class ViewBox extends GroupShadowNode { private static final int MOS_MEET = 0; private static final int MOS_SLICE = 1; private static final int MOS_NONE = 2; - static public Matrix getTransform(RectF vbRect, RectF eRect, String align, int meetOrSlice, boolean fromSymbol) { + static Matrix getTransform(RectF vbRect, RectF eRect, String align, int meetOrSlice) { // based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform // Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively. @@ -99,7 +99,7 @@ static public Matrix getTransform(RectF vbRect, RectF eRect, String align, int m // The transform applied to content contained by the element is given by // translate(translate-x, translate-y) scale(scale-x, scale-y). Matrix transform = new Matrix(); - transform.postTranslate(translateX * (fromSymbol ? scaleX : 1), translateY * (fromSymbol ? scaleY : 1)); + transform.postTranslate(translateX, translateY); transform.preScale(scaleX, scaleY); return transform; } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index f56354f4a..900030d4b 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015-present, Horcrux. * All rights reserved. * @@ -28,7 +28,7 @@ import static com.horcrux.svg.GlyphContext.DEFAULT_FONT_SIZE; -public abstract class VirtualNode extends LayoutShadowNode { +abstract class VirtualNode extends LayoutShadowNode { /* N[1/Sqrt[2], 36] The inverse of the square root of 2. @@ -36,26 +36,26 @@ public abstract class VirtualNode extends LayoutShadowNode { */ private static final double M_SQRT1_2l = 0.707106781186547524400844362104849039; - protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; + static final float MIN_OPACITY_FOR_DRAW = 0.01f; private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; - protected float mOpacity = 1f; - protected float mScaleX = 1f; - protected float mScaleY = 1f; - protected double mFontSize = -1; - protected double mParentFontSize = -1; - protected Matrix mMatrix = new Matrix(); + float mOpacity = 1f; + private float mScaleX = 1f; + private float mScaleY = 1f; + private double mFontSize = -1; + private double mParentFontSize = -1; + Matrix mMatrix = new Matrix(); private int mClipRule; - protected @Nullable String mClipPath; + private @Nullable String mClipPath; private static final int CLIP_RULE_EVENODD = 0; private static final int CLIP_RULE_NONZERO = 1; - protected final float mScale; - protected boolean mResponsible; - protected String mName; + final float mScale; + private boolean mResponsible; + String mName; private SvgViewShadowNode mSvgShadowNode; private Path mCachedClipPath; @@ -64,7 +64,7 @@ public abstract class VirtualNode extends LayoutShadowNode { private float canvasHeight = -1; private float canvasWidth = -1; - public VirtualNode() { + VirtualNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; } @@ -81,7 +81,7 @@ GroupShadowNode getTextRoot() { return shadowNode; } - GroupShadowNode getParentTextRoot() { + private GroupShadowNode getParentTextRoot() { GroupShadowNode shadowNode = getParentShadowNode(GroupShadowNode.class); if (shadowNode == null) { return getParentShadowNode(TextShadowNode.class); @@ -172,7 +172,7 @@ private GroupShadowNode getShadowNode(Class shadowNodeClass) { * * @param canvas the canvas to set up */ - protected int saveAndSetupCanvas(Canvas canvas) { + int saveAndSetupCanvas(Canvas canvas) { int count = canvas.save(); canvas.concat(mMatrix); return count; @@ -184,7 +184,7 @@ protected int saveAndSetupCanvas(Canvas canvas) { * * @param canvas the canvas to restore */ - protected void restoreCanvas(Canvas canvas, int count) { + void restoreCanvas(Canvas canvas, int count) { canvas.restoreToCount(count); } @@ -228,7 +228,7 @@ public void setScaleY(float scaleY) { @ReactProp(name = "matrix") public void setMatrix(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toFloatArray(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); if (matrixSize == 6) { sRawMatrix[0] = sMatrixData[0]; sRawMatrix[1] = sMatrixData[2]; @@ -262,17 +262,17 @@ public float getScaleY() { return mScaleY; } - @ReactProp(name = "responsible", defaultBoolean = false) + @ReactProp(name = "responsible") public void setResponsible(boolean responsible) { mResponsible = responsible; markUpdated(); } - protected @Nullable Path getClipPath() { + @Nullable Path getClipPath() { return mCachedClipPath; } - protected @Nullable Path getClipPath(Canvas canvas, Paint paint) { + @Nullable Path getClipPath(Canvas canvas, Paint paint) { if (mClipPath != null) { VirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPath); @@ -296,7 +296,7 @@ public void setResponsible(boolean responsible) { return getClipPath(); } - protected void clip(Canvas canvas, Paint paint) { + void clip(Canvas canvas, Paint paint) { Path clip = getClipPath(canvas, paint); if (clip != null) { @@ -312,7 +312,7 @@ public boolean isResponsible() { abstract protected Path getPath(Canvas canvas, Paint paint); - protected SvgViewShadowNode getSvgShadowNode() { + SvgViewShadowNode getSvgShadowNode() { if (mSvgShadowNode != null) { return mSvgShadowNode; } @@ -330,22 +330,22 @@ protected SvgViewShadowNode getSvgShadowNode() { return mSvgShadowNode; } - protected float relativeOnWidth(String length) { + float relativeOnWidth(String length) { return PropHelper.fromRelativeToFloat(length, getCanvasWidth(), 0, mScale, getFontSizeFromContext()); } - protected float relativeOnHeight(String length) { + float relativeOnHeight(String length) { return PropHelper.fromRelativeToFloat(length, getCanvasHeight(), 0, mScale, getFontSizeFromContext()); } - protected float relativeOnOther(String length) { + float relativeOnOther(String length) { double powX = Math.pow((getCanvasWidth()), 2); double powY = Math.pow((getCanvasHeight()), 2); float r = (float) (Math.sqrt(powX + powY) * M_SQRT1_2l); return PropHelper.fromRelativeToFloat(length, r, 0, mScale, getFontSizeFromContext()); } - protected float getCanvasWidth() { + private float getCanvasWidth() { if (canvasWidth != -1) { return canvasWidth; } @@ -359,7 +359,7 @@ protected float getCanvasWidth() { return canvasWidth; } - protected float getCanvasHeight() { + private float getCanvasHeight() { if (canvasHeight != -1) { return canvasHeight; } @@ -373,34 +373,32 @@ protected float getCanvasHeight() { return canvasHeight; } - protected float getCanvasLeft() { + float getCanvasLeft() { return getSvgShadowNode().getCanvasBounds().left; } - protected float getCanvasTop() { + float getCanvasTop() { return getSvgShadowNode().getCanvasBounds().top; } - protected void saveDefinition() { + void saveDefinition() { if (mName != null) { getSvgShadowNode().defineTemplate(this, mName); } } - protected interface NodeRunnable { - boolean run(VirtualNode node); + interface NodeRunnable { + void run(VirtualNode node); } - protected void traverseChildren(NodeRunnable runner) { + void traverseChildren(NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); if (!(child instanceof VirtualNode)) { continue; } - if (!runner.run((VirtualNode) child)) { - break; - } + runner.run((VirtualNode) child); } } } From 80e811e94b71d9272453349bc7842a6315eb97bb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 15:19:57 +0300 Subject: [PATCH 059/198] Decrease branching in GlyphContext hot-spots. Optimize getNextGlyphRotation, getNextDelta and getGlyphPosition. --- .../java/com/horcrux/svg/GlyphContext.java | 88 ++++++++++--------- .../horcrux/svg/RenderableViewManager.java | 32 +++---- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index ea8f147a5..26372e1ce 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -36,11 +36,11 @@ class GlyphContext { private final ArrayList mFontContext = new ArrayList<>(); private final ArrayList mRotationContext = new ArrayList<>(); private final ArrayList mNodes = new ArrayList<>(); + private @Nonnull final PointF mCurrentPosition = new PointF(); + private @Nonnull final PointF mCurrentDelta = new PointF(); private ArrayList mRotations = new ArrayList<>(); - private @Nonnull final PointF mCurrentLocation = new PointF(); private ArrayList mDeltaXs = new ArrayList<>(); private ArrayList mDeltaYs = new ArrayList<>(); - private @Nonnull final PointF mCurrentDelta = new PointF(); private ArrayList mXs = new ArrayList<>(); private ArrayList mYs = new ArrayList<>(); private int mContextLength; @@ -112,7 +112,7 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read private void reset() { mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; - mCurrentLocation.x = mCurrentLocation.y = 0; + mCurrentPosition.x = mCurrentPosition.y = 0; } void popContext() { @@ -144,10 +144,10 @@ void popContext() { } PointF getNextGlyphPoint(float glyphWidth) { - mCurrentLocation.x = getGlyphPosition(true); - mCurrentLocation.y = getGlyphPosition(false); - mCurrentLocation.x += glyphWidth; - return mCurrentLocation; + mCurrentPosition.x = getGlyphPosition(true); + mCurrentPosition.y = getGlyphPosition(false); + mCurrentPosition.x += glyphWidth; + return mCurrentPosition; } PointF getNextGlyphDelta() { @@ -157,21 +157,22 @@ PointF getNextGlyphDelta() { } float getNextGlyphRotation() { - List prev = null; + List context = mRotations; - for (int index = top; index >= 0; index--) { + if (context.size() != 0) { + mRotation = context.remove(0); + mRotationContext.set(top, mRotation); + } + + for (int index = top - 1; index >= 0; index--) { List rotations = mRotationsContext.get(index); - if (prev != rotations && rotations.size() != 0) { + if (context != rotations && rotations.size() != 0) { float val = rotations.remove(0); mRotationContext.set(index, val); - - if (index == top) { - mRotation = val; - } } - prev = rotations; + context = rotations; } return mRotation; @@ -180,20 +181,21 @@ float getNextGlyphRotation() { private float getNextDelta(boolean isX) { ArrayList> lists = isX ? mDeltaXsContext : mDeltaYsContext; float delta = isX ? mCurrentDelta.x : mCurrentDelta.y; - List prev = null; + List context = isX ? mDeltaXs : mDeltaYs; - for (int index = top; index >= 0; index--) { - List deltas = lists.get(index); + if (context.size() != 0) { + float val = context.remove(0); + delta += val * mScale; + } - if (prev != deltas && deltas.size() != 0) { - float val = deltas.remove(0); + for (int index = top - 1; index >= 0; index--) { + List deltas = lists.get(index); - if (top == index) { - delta += val * mScale; - } + if (context != deltas && deltas.size() != 0) { + deltas.remove(0); } - prev = deltas; + context = deltas; } return delta; @@ -201,27 +203,29 @@ private float getNextDelta(boolean isX) { private float getGlyphPosition(boolean isX) { ArrayList> lists = isX ? mXPositionsContext : mYPositionsContext; - float value = isX ? mCurrentLocation.x : mCurrentLocation.y; - List prev = null; + float value = isX ? mCurrentPosition.x : mCurrentPosition.y; + List context = isX ? mXs : mYs; + + if (context.size() != 0) { + String val = context.remove(0); + + float relative = isX ? mWidth : mHeight; + value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, getFontSize()); + if (isX) { + mCurrentDelta.x = 0; + } else { + mCurrentDelta.y = 0; + } + } - for (int index = top; index >= 0; index--) { - List list = lists.get(index); - - if (prev != list && list.size() != 0) { - String val = list.remove(0); - - if (top == index) { - float relative = isX ? mWidth : mHeight; - value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, getFontSize()); - if (isX) { - mCurrentDelta.x = 0; - } else { - mCurrentDelta.y = 0; - } - } + for (int index = top - 1; index >= 0; index--) { + List positions = lists.get(index); + + if (context != positions && positions.size() != 0) { + positions.remove(0); } - prev = list; + context = positions; } return value; diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 8805f7eed..4ecc38d5e 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -42,67 +42,67 @@ class RenderableViewManager extends ViewManager { private final String mClassName; - public static RenderableViewManager createGroupViewManager() { + static RenderableViewManager createGroupViewManager() { return new RenderableViewManager(CLASS_GROUP); } - public static RenderableViewManager createPathViewManager() { + static RenderableViewManager createPathViewManager() { return new RenderableViewManager(CLASS_PATH); } - public static RenderableViewManager createTextViewManager() { + static RenderableViewManager createTextViewManager() { return new RenderableViewManager(CLASS_TEXT); } - public static RenderableViewManager createTSpanViewManager() { + static RenderableViewManager createTSpanViewManager() { return new RenderableViewManager(CLASS_TSPAN); } - public static RenderableViewManager createTextPathViewManager() { + static RenderableViewManager createTextPathViewManager() { return new RenderableViewManager(CLASS_TEXT_PATH); } - public static RenderableViewManager createImageViewManager() { + static RenderableViewManager createImageViewManager() { return new RenderableViewManager(CLASS_IMAGE); } - public static RenderableViewManager createCircleViewManager() { + static RenderableViewManager createCircleViewManager() { return new RenderableViewManager(CLASS_CIRCLE); } - public static RenderableViewManager createEllipseViewManager() { + static RenderableViewManager createEllipseViewManager() { return new RenderableViewManager(CLASS_ELLIPSE); } - public static RenderableViewManager createLineViewManager() { + static RenderableViewManager createLineViewManager() { return new RenderableViewManager(CLASS_LINE); } - public static RenderableViewManager createRectViewManager() { + static RenderableViewManager createRectViewManager() { return new RenderableViewManager(CLASS_RECT); } - public static RenderableViewManager createClipPathViewManager() { + static RenderableViewManager createClipPathViewManager() { return new RenderableViewManager(CLASS_CLIP_PATH); } - public static RenderableViewManager createDefsViewManager() { + static RenderableViewManager createDefsViewManager() { return new RenderableViewManager(CLASS_DEFS); } - public static RenderableViewManager createUseViewManager() { + static RenderableViewManager createUseViewManager() { return new RenderableViewManager(CLASS_USE); } - public static RenderableViewManager createSymbolManager() { + static RenderableViewManager createSymbolManager() { return new RenderableViewManager(CLASS_SYMBOL); } - public static RenderableViewManager createLinearGradientManager() { + static RenderableViewManager createLinearGradientManager() { return new RenderableViewManager(CLASS_LINEAR_GRADIENT); } - public static RenderableViewManager createRadialGradientManager() { + static RenderableViewManager createRadialGradientManager() { return new RenderableViewManager(CLASS_RADIAL_GRADIENT); } From 9caae79e8b053e8f93c658c7e50eb27612f11be0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 15:27:51 +0300 Subject: [PATCH 060/198] Remove "-conformance" from version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e5cf13dc..770ebdd2e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.3.0-conformance", + "version": "5.3.0", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 6c81f1a3bb31c2ab7d351878aac304c78b92248f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 20:40:15 +0300 Subject: [PATCH 061/198] Optimize GlyphContext. Use float[] and String[] instead of ArrayList and ArrayList. Restrict mutations to int and ArrayList, use float[] and int index instead of ArrayList and .remove() to keep track of attribute value index. Makes several methods which were O(n^2) on the number of values in attribute list, into linear complexity on the number of ancestors defining attribute lists instead. --- .../java/com/horcrux/svg/GlyphContext.java | 323 ++++++++++++------ 1 file changed, 212 insertions(+), 111 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 26372e1ce..368a78c5d 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -17,8 +17,6 @@ import com.facebook.react.bridge.WritableMap; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -28,82 +26,152 @@ class GlyphContext { private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_LETTER_SPACING = 0f; - private final ArrayList> mXPositionsContext = new ArrayList<>(); - private final ArrayList> mYPositionsContext = new ArrayList<>(); - private final ArrayList> mRotationsContext = new ArrayList<>(); - private final ArrayList> mDeltaXsContext = new ArrayList<>(); - private final ArrayList> mDeltaYsContext = new ArrayList<>(); + // Unique input attribute lists (only added if node sets a value) + private final ArrayList mXPositionsContext = new ArrayList<>(); + private final ArrayList mYPositionsContext = new ArrayList<>(); + private final ArrayList mRotationsContext = new ArrayList<>(); + private final ArrayList mDeltaXsContext = new ArrayList<>(); + private final ArrayList mDeltaYsContext = new ArrayList<>(); + + // Unique index into attribute list (one per unique list) + private final ArrayList mXPositionIndices = new ArrayList<>(); + private final ArrayList mYPositionIndices = new ArrayList<>(); + private final ArrayList mRotationIndices = new ArrayList<>(); + private final ArrayList mDeltaXIndices = new ArrayList<>(); + private final ArrayList mDeltaYIndices = new ArrayList<>(); + + // Index of unique context used (one per node push/pop) + private final ArrayList mXPositionsIndices = new ArrayList<>(); + private final ArrayList mYPositionsIndices = new ArrayList<>(); + private final ArrayList mRotationsIndices = new ArrayList<>(); + private final ArrayList mDeltaXsIndices = new ArrayList<>(); + private final ArrayList mDeltaYsIndices = new ArrayList<>(); + + // Current stack (one per node push/pop) private final ArrayList mFontContext = new ArrayList<>(); - private final ArrayList mRotationContext = new ArrayList<>(); private final ArrayList mNodes = new ArrayList<>(); + + // Current accumulated values private @Nonnull final PointF mCurrentPosition = new PointF(); private @Nonnull final PointF mCurrentDelta = new PointF(); - private ArrayList mRotations = new ArrayList<>(); - private ArrayList mDeltaXs = new ArrayList<>(); - private ArrayList mDeltaYs = new ArrayList<>(); - private ArrayList mXs = new ArrayList<>(); - private ArrayList mYs = new ArrayList<>(); + + // Current attribute list + private float[] mRotations = new float[] {0}; + private float[] mDeltaXs = new float[] {}; + private float[] mDeltaYs = new float[] {}; + private String[] mXs = new String[] {}; + private String[] mYs = new String[] {}; + + // Current attribute list index + private int mXPositionsIndex; + private int mYPositionsIndex; + private int mRotationsIndex; + private int mDeltaXsIndex; + private int mDeltaYsIndex; + + // Current value index in current attribute list + private int mXPositionIndex = -1; + private int mYPositionIndex = -1; + private int mRotationIndex = -1; + private int mDeltaXIndex = -1; + private int mDeltaYIndex = -1; + private int mContextLength; - private float mRotation; - private final float mHeight; - private final float mWidth; - private final float mScale; private int top = -1; + // Constructor parameters + private final float mScale; + private final float mWidth; + private final float mHeight; + GlyphContext(float scale, float width, float height) { - mHeight = height; - mWidth = width; mScale = scale; + mWidth = width; + mHeight = height; + mXPositionsContext.add(mXs); + mYPositionsContext.add(mYs); + mDeltaXsContext.add(mDeltaXs); + mDeltaYsContext.add(mDeltaYs); + mRotationsContext.add(mRotations); + + mXPositionsIndices.add(mXPositionsIndex); + mYPositionsIndices.add(mYPositionsIndex); + mRotationsIndices.add(mRotationsIndex); + mDeltaXsIndices.add(mDeltaXsIndex); + mDeltaYsIndices.add(mDeltaYsIndex); + + mXPositionIndices.add(mXPositionIndex); + mYPositionIndices.add(mYPositionIndex); + mRotationIndices.add(mRotationIndex); + mDeltaXIndices.add(mDeltaXIndex); + mDeltaYIndices.add(mDeltaYIndex); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { - mRotationsContext.add(mRotations); - mRotationContext.add(mRotation); - mDeltaXsContext.add(mDeltaXs); - mDeltaYsContext.add(mDeltaYs); - mXPositionsContext.add(mXs); - mYPositionsContext.add(mYs); + mXPositionsIndices.add(mXPositionsIndex); + mYPositionsIndices.add(mYPositionsIndex); + mRotationsIndices.add(mRotationsIndex); + mDeltaXsIndices.add(mDeltaXsIndex); + mDeltaYsIndices.add(mDeltaYsIndex); mFontContext.add(font); mNodes.add(node); mContextLength++; top++; } - void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY, boolean resetLocation) { - if (resetLocation) { + void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY, boolean resetPosition) { + if (resetPosition) { reset(); } if (positionX != null) { - mXs = new ArrayList<>(Arrays.asList(positionX.trim().split("\\s+"))); + mXPositionsIndex++; + mXPositionIndex = -1; + mXs = positionX.trim().split("\\s+"); + mXPositionIndices.add(mXPositionIndex); + mXPositionsContext.add(mXs); } if (positionY != null) { - mYs = new ArrayList<>(Arrays.asList(positionY.trim().split("\\s+"))); + mYPositionsIndex++; + mYPositionIndex = -1; + mYs = positionY.trim().split("\\s+"); + mYPositionIndices.add(mYPositionIndex); + mYPositionsContext.add(mYs); } - ArrayList rotations = getFloatArrayListFromReadableArray(rotate); - if (rotations.size() != 0) { - mRotation = rotations.get(0); + float[] rotations = getFloatArrayFromReadableArray(rotate); + if (rotations.length != 0) { + mRotationsIndex++; + mRotationIndex = -1; mRotations = rotations; + mRotationsContext.add(mRotations); + mRotationIndices.add(mRotationIndex); } - ArrayList deltaXs = getFloatArrayListFromReadableArray(deltaX); - if (deltaXs.size() != 0) { + float[] deltaXs = getFloatArrayFromReadableArray(deltaX); + if (deltaXs.length != 0) { + mDeltaXsIndex++; + mDeltaXIndex = -1; mDeltaXs = deltaXs; + mDeltaXsContext.add(mDeltaXs); + mDeltaXIndices.add(mDeltaXIndex); } - ArrayList deltaYs = getFloatArrayListFromReadableArray(deltaY); - if (deltaYs.size() != 0) { + float[] deltaYs = getFloatArrayFromReadableArray(deltaY); + if (deltaYs.length != 0) { + mDeltaYsIndex++; + mDeltaYIndex = -1; mDeltaYs = deltaYs; + mDeltaYsContext.add(mDeltaYs); + mDeltaYIndices.add(mDeltaYIndex); } - mRotationsContext.add(mRotations); - mRotationContext.add(mRotation); - mDeltaXsContext.add(mDeltaXs); - mDeltaYsContext.add(mDeltaYs); - mXPositionsContext.add(mXs); - mYPositionsContext.add(mYs); + mXPositionsIndices.add(mXPositionsIndex); + mYPositionsIndices.add(mYPositionsIndex); + mRotationsIndices.add(mRotationsIndex); + mDeltaXsIndices.add(mDeltaXsIndex); + mDeltaYsIndices.add(mDeltaYsIndex); mFontContext.add(font); mNodes.add(node); mContextLength++; @@ -111,36 +179,75 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read } private void reset() { - mCurrentDelta.x = mCurrentDelta.y = mRotation = 0; - mCurrentPosition.x = mCurrentPosition.y = 0; + mXPositionsIndex = mYPositionsIndex = mRotationsIndex = mDeltaXsIndex = mDeltaYsIndex = 0; + mXPositionIndex = mYPositionIndex = mRotationIndex = mDeltaXIndex = mDeltaYIndex = -1; + mCurrentPosition.x = mCurrentPosition.y = mCurrentDelta.x = mCurrentDelta.y = 0; } void popContext() { mContextLength--; top--; - mXPositionsContext.remove(mContextLength); - mYPositionsContext.remove(mContextLength); - - mRotationsContext.remove(mContextLength); - mRotationContext.remove(mContextLength); - - mDeltaXsContext.remove(mContextLength); - mDeltaYsContext.remove(mContextLength); - mFontContext.remove(mContextLength); mNodes.remove(mContextLength); - if (mContextLength != 0) { - mRotations = mRotationsContext.get(top); - mRotation = mRotationContext.get(top); - mDeltaXs = mDeltaXsContext.get(top); - mDeltaYs = mDeltaYsContext.get(top); - mXs = mXPositionsContext.get(top); - mYs = mYPositionsContext.get(top); - } else { + mXPositionsIndices.remove(mContextLength); + mYPositionsIndices.remove(mContextLength); + mRotationsIndices.remove(mContextLength); + mDeltaXsIndices.remove(mContextLength); + mDeltaYsIndices.remove(mContextLength); + + int x = mXPositionsIndex; + int y = mYPositionsIndex; + int r = mRotationsIndex; + int dx = mDeltaXsIndex; + int dy = mDeltaYsIndex; + + if (mContextLength == 0) { reset(); } + + mXPositionsIndex = mXPositionsIndices.get(mContextLength); + mYPositionsIndex = mYPositionsIndices.get(mContextLength); + mRotationsIndex = mRotationsIndices.get(mContextLength); + mDeltaXsIndex = mDeltaXsIndices.get(mContextLength); + mDeltaYsIndex = mDeltaYsIndices.get(mContextLength); + + if (x != mXPositionsIndex) { + mXPositionsContext.remove(x); + if (mXPositionsIndex > -1) { + mXs = mXPositionsContext.get(mXPositionsIndex); + mXPositionIndex = mXPositionIndices.get(mXPositionsIndex); + } + } + if (y != mYPositionsIndex) { + mYPositionsContext.remove(y); + if (mYPositionsIndex > -1) { + mYs = mYPositionsContext.get(mYPositionsIndex); + mYPositionIndex = mYPositionIndices.get(mYPositionsIndex); + } + } + if (r != mRotationsIndex) { + mRotationsContext.remove(r); + if (mRotationsIndex > -1) { + mRotations = mRotationsContext.get(mRotationsIndex); + mRotationIndex = mRotationIndices.get(mRotationsIndex); + } + } + if (dx != mDeltaXsIndex) { + mDeltaXsContext.remove(dx); + if (mDeltaXsIndex > -1) { + mDeltaXs = mDeltaXsContext.get(mDeltaXsIndex); + mDeltaXIndex = mDeltaXIndices.get(mDeltaXsIndex); + } + } + if (dy != mDeltaYsIndex) { + mDeltaYsContext.remove(dy); + if (mDeltaYsIndex > -1) { + mDeltaYs = mDeltaYsContext.get(mDeltaYsIndex); + mDeltaYIndex = mDeltaYIndices.get(mDeltaYsIndex); + } + } } PointF getNextGlyphPoint(float glyphWidth) { @@ -157,75 +264,68 @@ PointF getNextGlyphDelta() { } float getNextGlyphRotation() { - List context = mRotations; + float[] context = mRotations; - if (context.size() != 0) { - mRotation = context.remove(0); - mRotationContext.set(top, mRotation); + for (int index = mRotationsIndex; index >= 0; index--) { + int rotationIndex = mRotationIndices.get(index); + mRotationIndices.set(index, rotationIndex + 1); } - for (int index = top - 1; index >= 0; index--) { - List rotations = mRotationsContext.get(index); - - if (context != rotations && rotations.size() != 0) { - float val = rotations.remove(0); - mRotationContext.set(index, val); - } - - context = rotations; - } + mRotationIndex = Math.min(mRotationIndex + 1, context.length - 1); - return mRotation; + return context[mRotationIndex]; } private float getNextDelta(boolean isX) { - ArrayList> lists = isX ? mDeltaXsContext : mDeltaYsContext; + ArrayList deltaIndices = isX ? mDeltaXIndices : mDeltaYIndices; + int deltasIndex = isX ? mDeltaXsIndex : mDeltaYsIndex; float delta = isX ? mCurrentDelta.x : mCurrentDelta.y; - List context = isX ? mDeltaXs : mDeltaYs; + int currentIndex = isX ? mDeltaXIndex : mDeltaYIndex; + float[] context = isX ? mDeltaXs : mDeltaYs; - if (context.size() != 0) { - float val = context.remove(0); - delta += val * mScale; + for (int index = deltasIndex; index >= 0; index--) { + int deltaIndex = deltaIndices.get(index); + deltaIndices.set(index, deltaIndex + 1); } - for (int index = top - 1; index >= 0; index--) { - List deltas = lists.get(index); - - if (context != deltas && deltas.size() != 0) { - deltas.remove(0); + int nextIndex = currentIndex + 1; + if (nextIndex < context.length) { + if (isX) { + mDeltaXIndex = nextIndex; + } else { + mDeltaYIndex = nextIndex; } - - context = deltas; + float val = context[nextIndex]; + delta += val * mScale; } return delta; } private float getGlyphPosition(boolean isX) { - ArrayList> lists = isX ? mXPositionsContext : mYPositionsContext; + ArrayList positionIndices = isX ? mXPositionIndices : mYPositionIndices; + int positionsIndex = isX ? mXPositionsIndex : mYPositionsIndex; float value = isX ? mCurrentPosition.x : mCurrentPosition.y; - List context = isX ? mXs : mYs; + int currentIndex = isX ? mXPositionIndex : mYPositionIndex; + String[] context = isX ? mXs : mYs; - if (context.size() != 0) { - String val = context.remove(0); + for (int index = positionsIndex; index >= 0; index--) { + int positionIndex = positionIndices.get(index); + positionIndices.set(index, positionIndex + 1); + } - float relative = isX ? mWidth : mHeight; - value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, getFontSize()); + int nextIndex = currentIndex + 1; + if (nextIndex < context.length) { if (isX) { mCurrentDelta.x = 0; + mXPositionIndex = nextIndex; } else { mCurrentDelta.y = 0; + mYPositionIndex = nextIndex; } - } - - for (int index = top - 1; index >= 0; index--) { - List positions = lists.get(index); - - if (context != positions && positions.size() != 0) { - positions.remove(0); - } - - context = positions; + String val = context[nextIndex]; + float relative = isX ? mWidth : mHeight; + value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, getFontSize()); } return value; @@ -310,24 +410,25 @@ ReadableMap getGlyphFont() { return map; } - private ArrayList getFloatArrayListFromReadableArray(ReadableArray readableArray) { - ArrayList arrayList = new ArrayList<>(); - + private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { if (readableArray != null) { - for (int i = 0; i < readableArray.size(); i++) { + int size = readableArray.size(); + float[] floats = new float[size]; + for (int i = 0; i < size; i++) { switch (readableArray.getType(i)) { case String: String val = readableArray.getString(i); - arrayList.add((float) (Float.valueOf(val.substring(0, val.length() - 2)) * getFontSize())); + floats[i] = (float) (Float.valueOf(val.substring(0, val.length() - 2)) * getFontSize()); break; case Number: - arrayList.add((float) readableArray.getDouble(i)); + floats[i] = (float) readableArray.getDouble(i); break; } } + return floats; } - return arrayList; + return new float[0]; } } From 1b60081b11abbef0e45e3c35f2fd8f8b3b4b37b1 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 21:00:47 +0300 Subject: [PATCH 062/198] Refactor common code into pushIndices method. --- .../java/com/horcrux/svg/GlyphContext.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 368a78c5d..dac245d19 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -88,34 +88,27 @@ class GlyphContext { mScale = scale; mWidth = width; mHeight = height; + mXPositionsContext.add(mXs); mYPositionsContext.add(mYs); mDeltaXsContext.add(mDeltaXs); mDeltaYsContext.add(mDeltaYs); mRotationsContext.add(mRotations); - mXPositionsIndices.add(mXPositionsIndex); - mYPositionsIndices.add(mYPositionsIndex); - mRotationsIndices.add(mRotationsIndex); - mDeltaXsIndices.add(mDeltaXsIndex); - mDeltaYsIndices.add(mDeltaYsIndex); - mXPositionIndices.add(mXPositionIndex); mYPositionIndices.add(mYPositionIndex); mRotationIndices.add(mRotationIndex); mDeltaXIndices.add(mDeltaXIndex); mDeltaYIndices.add(mDeltaYIndex); + + pushIndices(); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { - mXPositionsIndices.add(mXPositionsIndex); - mYPositionsIndices.add(mYPositionsIndex); - mRotationsIndices.add(mRotationsIndex); - mDeltaXsIndices.add(mDeltaXsIndex); - mDeltaYsIndices.add(mDeltaYsIndex); mFontContext.add(font); mNodes.add(node); mContextLength++; + pushIndices(); top++; } @@ -167,15 +160,19 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read mDeltaYIndices.add(mDeltaYIndex); } + mFontContext.add(font); + mNodes.add(node); + mContextLength++; + pushIndices(); + top++; + } + + private void pushIndices() { mXPositionsIndices.add(mXPositionsIndex); mYPositionsIndices.add(mYPositionsIndex); mRotationsIndices.add(mRotationsIndex); mDeltaXsIndices.add(mDeltaXsIndex); mDeltaYsIndices.add(mDeltaYsIndex); - mFontContext.add(font); - mNodes.add(node); - mContextLength++; - top++; } private void reset() { From c5d5ce18f781067688230b9c4c7128f174c4b5de Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 21:07:20 +0300 Subject: [PATCH 063/198] Remove redundant if checks. --- .../java/com/horcrux/svg/GlyphContext.java | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index dac245d19..4000056f2 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -212,38 +212,28 @@ void popContext() { if (x != mXPositionsIndex) { mXPositionsContext.remove(x); - if (mXPositionsIndex > -1) { - mXs = mXPositionsContext.get(mXPositionsIndex); - mXPositionIndex = mXPositionIndices.get(mXPositionsIndex); - } + mXs = mXPositionsContext.get(mXPositionsIndex); + mXPositionIndex = mXPositionIndices.get(mXPositionsIndex); } if (y != mYPositionsIndex) { mYPositionsContext.remove(y); - if (mYPositionsIndex > -1) { - mYs = mYPositionsContext.get(mYPositionsIndex); - mYPositionIndex = mYPositionIndices.get(mYPositionsIndex); - } + mYs = mYPositionsContext.get(mYPositionsIndex); + mYPositionIndex = mYPositionIndices.get(mYPositionsIndex); } if (r != mRotationsIndex) { mRotationsContext.remove(r); - if (mRotationsIndex > -1) { - mRotations = mRotationsContext.get(mRotationsIndex); - mRotationIndex = mRotationIndices.get(mRotationsIndex); - } + mRotations = mRotationsContext.get(mRotationsIndex); + mRotationIndex = mRotationIndices.get(mRotationsIndex); } if (dx != mDeltaXsIndex) { mDeltaXsContext.remove(dx); - if (mDeltaXsIndex > -1) { - mDeltaXs = mDeltaXsContext.get(mDeltaXsIndex); - mDeltaXIndex = mDeltaXIndices.get(mDeltaXsIndex); - } + mDeltaXs = mDeltaXsContext.get(mDeltaXsIndex); + mDeltaXIndex = mDeltaXIndices.get(mDeltaXsIndex); } if (dy != mDeltaYsIndex) { mDeltaYsContext.remove(dy); - if (mDeltaYsIndex > -1) { - mDeltaYs = mDeltaYsContext.get(mDeltaYsIndex); - mDeltaYIndex = mDeltaYIndices.get(mDeltaYsIndex); - } + mDeltaYs = mDeltaYsContext.get(mDeltaYsIndex); + mDeltaYIndex = mDeltaYIndices.get(mDeltaYsIndex); } } From 546fbc38caec06d0cbb432be35e116c48da07279 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 21:52:52 +0300 Subject: [PATCH 064/198] Cache fontSize in GlyphContext --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 4000056f2..3e3ebf435 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -54,6 +54,7 @@ class GlyphContext { // Current accumulated values private @Nonnull final PointF mCurrentPosition = new PointF(); private @Nonnull final PointF mCurrentDelta = new PointF(); + private double fontSize = DEFAULT_FONT_SIZE; // Current attribute list private float[] mRotations = new float[] {0}; @@ -110,6 +111,8 @@ void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { mContextLength++; pushIndices(); top++; + + fontSize = getFontSize(); } void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY, boolean resetPosition) { @@ -165,6 +168,8 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read mContextLength++; pushIndices(); top++; + + fontSize = getFontSize(); } private void pushIndices() { @@ -312,7 +317,7 @@ private float getGlyphPosition(boolean isX) { } String val = context[nextIndex]; float relative = isX ? mWidth : mHeight; - value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, getFontSize()); + value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, fontSize); } return value; @@ -344,7 +349,6 @@ float getHeight() { ReadableMap getGlyphFont() { float letterSpacing = DEFAULT_LETTER_SPACING; - float fontSize = (float) getFontSize(); float kerning = DEFAULT_KERNING; boolean letterSpacingSet = false; @@ -405,7 +409,7 @@ private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { switch (readableArray.getType(i)) { case String: String val = readableArray.getString(i); - floats[i] = (float) (Float.valueOf(val.substring(0, val.length() - 2)) * getFontSize()); + floats[i] = (float) (Float.valueOf(val.substring(0, val.length() - 2)) * fontSize); break; case Number: From 60d956cb6c077b829d5187093035b96962cb28a9 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 23:38:28 +0300 Subject: [PATCH 065/198] Optimize pushContext and bytecode of getNext-Point/Delta/Rotation. --- .../java/com/horcrux/svg/GlyphContext.java | 153 +++++++++--------- 1 file changed, 75 insertions(+), 78 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 3e3ebf435..76cfb4abc 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -136,31 +136,28 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read mYPositionsContext.add(mYs); } - float[] rotations = getFloatArrayFromReadableArray(rotate); - if (rotations.length != 0) { + if (rotate != null && rotate.size() != 0) { mRotationsIndex++; mRotationIndex = -1; - mRotations = rotations; - mRotationsContext.add(mRotations); mRotationIndices.add(mRotationIndex); + mRotations = getFloatArrayFromReadableArray(rotate); + mRotationsContext.add(mRotations); } - float[] deltaXs = getFloatArrayFromReadableArray(deltaX); - if (deltaXs.length != 0) { + if (deltaX != null && deltaX.size() != 0) { mDeltaXsIndex++; mDeltaXIndex = -1; - mDeltaXs = deltaXs; - mDeltaXsContext.add(mDeltaXs); mDeltaXIndices.add(mDeltaXIndex); + mDeltaXs = getFloatArrayFromReadableArray(deltaX); + mDeltaXsContext.add(mDeltaXs); } - float[] deltaYs = getFloatArrayFromReadableArray(deltaY); - if (deltaYs.length != 0) { + if (deltaY != null && deltaY.size() != 0) { mDeltaYsIndex++; mDeltaYIndex = -1; - mDeltaYs = deltaYs; - mDeltaYsContext.add(mDeltaYs); mDeltaYIndices.add(mDeltaYIndex); + mDeltaYs = getFloatArrayFromReadableArray(deltaY); + mDeltaYsContext.add(mDeltaYs); } mFontContext.add(font); @@ -243,84 +240,88 @@ void popContext() { } PointF getNextGlyphPoint(float glyphWidth) { - mCurrentPosition.x = getGlyphPosition(true); - mCurrentPosition.y = getGlyphPosition(false); + setGlyphPositionX(); + setGlyphPositionY(); mCurrentPosition.x += glyphWidth; return mCurrentPosition; } PointF getNextGlyphDelta() { - mCurrentDelta.x = getNextDelta(true); - mCurrentDelta.y = getNextDelta(false); + setNextDeltaX(); + setNextDeltaY(); return mCurrentDelta; } float getNextGlyphRotation() { - float[] context = mRotations; + setNextGlyphRotation(); + return mRotations[mRotationIndex]; + } + private void setNextGlyphRotation() { for (int index = mRotationsIndex; index >= 0; index--) { int rotationIndex = mRotationIndices.get(index); mRotationIndices.set(index, rotationIndex + 1); } - mRotationIndex = Math.min(mRotationIndex + 1, context.length - 1); - - return context[mRotationIndex]; + mRotationIndex = Math.min(mRotationIndex + 1, mRotations.length - 1); } - private float getNextDelta(boolean isX) { - ArrayList deltaIndices = isX ? mDeltaXIndices : mDeltaYIndices; - int deltasIndex = isX ? mDeltaXsIndex : mDeltaYsIndex; - float delta = isX ? mCurrentDelta.x : mCurrentDelta.y; - int currentIndex = isX ? mDeltaXIndex : mDeltaYIndex; - float[] context = isX ? mDeltaXs : mDeltaYs; + private void setNextDeltaX() { + for (int index = mDeltaXsIndex; index >= 0; index--) { + int deltaIndex = mDeltaXIndices.get(index); + mDeltaXIndices.set(index, deltaIndex + 1); + } - for (int index = deltasIndex; index >= 0; index--) { - int deltaIndex = deltaIndices.get(index); - deltaIndices.set(index, deltaIndex + 1); + int nextIndex = mDeltaXIndex + 1; + if (nextIndex < mDeltaXs.length) { + mDeltaXIndex = nextIndex; + float val = mDeltaXs[nextIndex]; + mCurrentDelta.x += val * mScale; } + } - int nextIndex = currentIndex + 1; - if (nextIndex < context.length) { - if (isX) { - mDeltaXIndex = nextIndex; - } else { - mDeltaYIndex = nextIndex; - } - float val = context[nextIndex]; - delta += val * mScale; + private void setNextDeltaY() { + for (int index = mDeltaYsIndex; index >= 0; index--) { + int deltaIndex = mDeltaYIndices.get(index); + mDeltaYIndices.set(index, deltaIndex + 1); } - return delta; + int nextIndex = mDeltaYIndex + 1; + if (nextIndex < mDeltaYs.length) { + mDeltaYIndex = nextIndex; + float val = mDeltaYs[nextIndex]; + mCurrentDelta.y += val * mScale; + } } - private float getGlyphPosition(boolean isX) { - ArrayList positionIndices = isX ? mXPositionIndices : mYPositionIndices; - int positionsIndex = isX ? mXPositionsIndex : mYPositionsIndex; - float value = isX ? mCurrentPosition.x : mCurrentPosition.y; - int currentIndex = isX ? mXPositionIndex : mYPositionIndex; - String[] context = isX ? mXs : mYs; + private void setGlyphPositionX() { + for (int index = mXPositionsIndex; index >= 0; index--) { + int positionIndex = mXPositionIndices.get(index); + mXPositionIndices.set(index, positionIndex + 1); + } - for (int index = positionsIndex; index >= 0; index--) { - int positionIndex = positionIndices.get(index); - positionIndices.set(index, positionIndex + 1); + int nextIndex = mXPositionIndex + 1; + if (nextIndex < mXs.length) { + mCurrentDelta.x = 0; + mXPositionIndex = nextIndex; + String val = mXs[nextIndex]; + mCurrentPosition.x = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, fontSize); } + } - int nextIndex = currentIndex + 1; - if (nextIndex < context.length) { - if (isX) { - mCurrentDelta.x = 0; - mXPositionIndex = nextIndex; - } else { - mCurrentDelta.y = 0; - mYPositionIndex = nextIndex; - } - String val = context[nextIndex]; - float relative = isX ? mWidth : mHeight; - value = PropHelper.fromRelativeToFloat(val, relative, 0, mScale, fontSize); + private void setGlyphPositionY() { + for (int index = mYPositionsIndex; index >= 0; index--) { + int positionIndex = mYPositionIndices.get(index); + mYPositionIndices.set(index, positionIndex + 1); } - return value; + int nextIndex = mYPositionIndex + 1; + if (nextIndex < mYs.length) { + mCurrentDelta.y = 0; + mYPositionIndex = nextIndex; + String val = mYs[nextIndex]; + mCurrentPosition.y = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, fontSize); + } } float getWidth() { @@ -402,24 +403,20 @@ ReadableMap getGlyphFont() { } private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { - if (readableArray != null) { - int size = readableArray.size(); - float[] floats = new float[size]; - for (int i = 0; i < size; i++) { - switch (readableArray.getType(i)) { - case String: - String val = readableArray.getString(i); - floats[i] = (float) (Float.valueOf(val.substring(0, val.length() - 2)) * fontSize); - break; - - case Number: - floats[i] = (float) readableArray.getDouble(i); - break; - } + int size = readableArray.size(); + float[] floats = new float[size]; + for (int i = 0; i < size; i++) { + switch (readableArray.getType(i)) { + case String: + String val = readableArray.getString(i); + floats[i] = (float) (Float.valueOf(val.substring(0, val.length() - 2)) * fontSize); + break; + + case Number: + floats[i] = (float) readableArray.getDouble(i); + break; } - return floats; } - - return new float[0]; + return floats; } } From cece2721ed68ee7837f5ae7470c16b25edc53db9 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 21 Jul 2017 23:53:02 +0300 Subject: [PATCH 066/198] Fix formatting --- .../java/com/horcrux/svg/GlyphContext.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 76cfb4abc..93447cd62 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -57,11 +57,11 @@ class GlyphContext { private double fontSize = DEFAULT_FONT_SIZE; // Current attribute list - private float[] mRotations = new float[] {0}; - private float[] mDeltaXs = new float[] {}; - private float[] mDeltaYs = new float[] {}; - private String[] mXs = new String[] {}; - private String[] mYs = new String[] {}; + private float[] mRotations = new float[]{0}; + private float[] mDeltaXs = new float[]{}; + private float[] mDeltaYs = new float[]{}; + private String[] mXs = new String[]{}; + private String[] mYs = new String[]{}; // Current attribute list index private int mXPositionsIndex; @@ -275,7 +275,7 @@ private void setNextDeltaX() { int nextIndex = mDeltaXIndex + 1; if (nextIndex < mDeltaXs.length) { mDeltaXIndex = nextIndex; - float val = mDeltaXs[nextIndex]; + float val = mDeltaXs[nextIndex]; mCurrentDelta.x += val * mScale; } } @@ -289,7 +289,7 @@ private void setNextDeltaY() { int nextIndex = mDeltaYIndex + 1; if (nextIndex < mDeltaYs.length) { mDeltaYIndex = nextIndex; - float val = mDeltaYs[nextIndex]; + float val = mDeltaYs[nextIndex]; mCurrentDelta.y += val * mScale; } } @@ -304,7 +304,7 @@ private void setGlyphPositionX() { if (nextIndex < mXs.length) { mCurrentDelta.x = 0; mXPositionIndex = nextIndex; - String val = mXs[nextIndex]; + String val = mXs[nextIndex]; mCurrentPosition.x = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, fontSize); } } @@ -319,7 +319,7 @@ private void setGlyphPositionY() { if (nextIndex < mYs.length) { mCurrentDelta.y = 0; mYPositionIndex = nextIndex; - String val = mYs[nextIndex]; + String val = mYs[nextIndex]; mCurrentPosition.y = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, fontSize); } } From 1a5610dfa9c41853126b68c3dc7c8eb690272a19 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 00:36:32 +0300 Subject: [PATCH 067/198] Cache getTextRootGlyphContext, simplify. --- .../java/com/horcrux/svg/GroupShadowNode.java | 24 +++------ .../com/horcrux/svg/RenderableShadowNode.java | 2 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 49 +++++++++---------- .../java/com/horcrux/svg/TextShadowNode.java | 4 +- 4 files changed, 33 insertions(+), 46 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index ab200b817..fab81ec74 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -47,28 +47,16 @@ GlyphContext getGlyphContext() { return mGlyphContext; } - void pushGlyphContext() { - getTextRoot().getGlyphContext().pushContext(this, mFont); - } - - void popGlyphContext() { - getTextRoot().getGlyphContext().popContext(); - } - - ReadableMap getFontFromContext() { - return getTextRoot().getGlyphContext().getGlyphFont(); - } - - PointF getGlyphPointFromContext(float glyphWidth) { - return getTextRoot().getGlyphContext().getNextGlyphPoint(glyphWidth); + GlyphContext getTextRootGlyphContext() { + return getTextRoot().getGlyphContext(); } - PointF getGlyphDeltaFromContext() { - return getTextRoot().getGlyphContext().getNextGlyphDelta(); + void pushGlyphContext() { + getTextRootGlyphContext().pushContext(this, mFont); } - float getNextGlyphRotationFromContext() { - return getTextRoot().getGlyphContext().getNextGlyphRotation(); + void popGlyphContext() { + getTextRootGlyphContext().popContext(); } public void draw(final Canvas canvas, final Paint paint, final float opacity) { diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index c6b4f0e10..5f6cbce42 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -127,7 +127,7 @@ public void setStrokeDasharray(@Nullable ReadableArray strokeDasharray) { markUpdated(); } - @ReactProp(name = "strokeDashoffset", defaultFloat = 0f) + @ReactProp(name = "strokeDashoffset") public void setStrokeDashoffset(float strokeWidth) { mStrokeDashoffset = strokeWidth * mScale; markUpdated(); diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 8065c98c7..01254d40d 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -104,7 +104,6 @@ private float getTextAnchorShift(float width) { } private Path getLinePath(String line, Paint paint) { - ReadableMap font = applyTextPropertiesToPaint(paint); int length = line.length(); Path path = new Path(); @@ -112,8 +111,12 @@ private Path getLinePath(String line, Paint paint) { return path; } + GlyphContext gc = getTextRootGlyphContext(); + ReadableMap font = gc.getGlyphFont(); + applyTextPropertiesToPaint(paint, font); + float distance = 0; - float startOffset = 0; + float offset = 0; float renderMethodScaling = 1; float textMeasure = paint.measureText(line); @@ -121,7 +124,7 @@ private Path getLinePath(String line, Paint paint) { if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - startOffset = PropHelper.fromRelativeToFloat(textPath.getStartOffset(), distance, 0, mScale, getFontSizeFromContext()); + offset = PropHelper.fromRelativeToFloat(textPath.getStartOffset(), distance, 0, mScale, getFontSizeFromContext()); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" String method = textPath.getMethod(); // method = "align | stretch" if ("stretch".equals(method)) { @@ -129,20 +132,20 @@ private Path getLinePath(String line, Paint paint) { } } - startOffset += getTextAnchorShift(textMeasure); + offset += getTextAnchorShift(textMeasure); Path glyph; float width; + PointF point; + PointF delta; Matrix matrix; String current; - PointF glyphPoint; - PointF glyphDelta; - float glyphRotation; + float rotation; String previous = ""; float previousWidth = 0; char[] chars = line.toCharArray(); - float kerningValue = (float) (font.getDouble(PROP_KERNING) * mScale); - boolean isKerningValueSet = font.getBoolean(PROP_IS_KERNING_VALUE_SET); + float kerning = (float) (font.getDouble(PROP_KERNING) * mScale); + boolean autoKerning = !font.getBoolean(PROP_IS_KERNING_VALUE_SET); for (int index = 0; index < length; index++) { glyph = new Path(); @@ -150,19 +153,19 @@ private Path getLinePath(String line, Paint paint) { paint.getTextPath(current, 0, 1, 0, 0, glyph); width = paint.measureText(current) * renderMethodScaling; - if (!isKerningValueSet) { - float bothWidth = paint.measureText(previous + current) * renderMethodScaling; - kerningValue = bothWidth - previousWidth - width; + if (autoKerning) { + float both = paint.measureText(previous + current) * renderMethodScaling; + kerning = both - previousWidth - width; previousWidth = width; previous = current; } - glyphPoint = getGlyphPointFromContext(width + kerningValue); - glyphRotation = getNextGlyphRotationFromContext(); - glyphDelta = getGlyphDeltaFromContext(); + point = gc.getNextGlyphPoint(width + kerning); + rotation = gc.getNextGlyphRotation(); + delta = gc.getNextGlyphDelta(); matrix = new Matrix(); - float x = startOffset + glyphPoint.x + glyphDelta.x - width; + float x = offset + point.x + delta.x - width; if (textPath != null) { float halfway = width / 2; @@ -177,15 +180,15 @@ private Path getLinePath(String line, Paint paint) { assert pm != null; pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - matrix.preTranslate(-halfway, glyphDelta.y); + matrix.preTranslate(-halfway, delta.y); matrix.preScale(renderMethodScaling, 1); - matrix.postTranslate(0, glyphPoint.y); + matrix.postTranslate(0, point.y); } else { - float y = glyphPoint.y + glyphDelta.y; + float y = point.y + delta.y; matrix.setTranslate(x, y); } - matrix.preRotate(glyphRotation); + matrix.preRotate(rotation); glyph.transform(matrix); path.addPath(glyph); } @@ -193,9 +196,7 @@ private Path getLinePath(String line, Paint paint) { return path; } - private ReadableMap applyTextPropertiesToPaint(Paint paint) { - ReadableMap font = getFontFromContext(); - + private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { paint.setTextAlign(Paint.Align.LEFT); float fontSize = (float) font.getDouble(PROP_FONT_SIZE) * mScale; @@ -243,8 +244,6 @@ private ReadableMap applyTextPropertiesToPaint(Paint paint) { // NB: if the font family is null / unsupported, the default one will be used paint.setTypeface(tf); - - return font; } private void setupTextPath() { diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 170a781a9..1bf6446fb 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -45,13 +45,13 @@ class TextShadowNode extends GroupShadowNode { private @Nullable String mPositionX; private @Nullable String mPositionY; - @ReactProp(name = "textAnchor", defaultInt = TEXT_ANCHOR_AUTO) + @ReactProp(name = "textAnchor") public void setTextAnchor(int textAnchor) { mTextAnchor = textAnchor; markUpdated(); } - @ReactProp(name = "textDecoration", defaultInt = TEXT_DECORATION_NONE) + @ReactProp(name = "textDecoration") public void setTextDecoration(int textDecoration) { mTextDecoration = textDecoration; markUpdated(); From ec2540a70c96d1106f1781644ec5a50524c06869 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 00:55:10 +0300 Subject: [PATCH 068/198] Fix warnings, cleanup. --- .../java/com/horcrux/svg/GroupShadowNode.java | 1 - .../java/com/horcrux/svg/PathShadowNode.java | 7 +---- .../main/java/com/horcrux/svg/PropHelper.java | 7 ----- .../com/horcrux/svg/TextPathShadowNode.java | 2 +- .../java/com/horcrux/svg/UseShadowNode.java | 8 ------ .../java/com/horcrux/svg/VirtualNode.java | 26 ------------------- 6 files changed, 2 insertions(+), 49 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index fab81ec74..72173c3bc 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -14,7 +14,6 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; -import android.graphics.PointF; import android.graphics.RectF; import com.facebook.react.bridge.ReadableMap; diff --git a/android/src/main/java/com/horcrux/svg/PathShadowNode.java b/android/src/main/java/com/horcrux/svg/PathShadowNode.java index 17d3de880..2771864be 100644 --- a/android/src/main/java/com/horcrux/svg/PathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/PathShadowNode.java @@ -13,7 +13,6 @@ import android.graphics.Paint; import android.graphics.Path; -import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.annotations.ReactProp; /** @@ -22,11 +21,10 @@ class PathShadowNode extends RenderableShadowNode { private Path mPath; - private PropHelper.PathParser mD; @ReactProp(name = "d") public void setD(String d) { - mD = new PropHelper.PathParser(d, mScale); + PropHelper.PathParser mD = new PropHelper.PathParser(d, mScale); mPath = mD.getPath(); markUpdated(); } @@ -40,7 +38,4 @@ public Path getPath() { return mPath; } - public ReadableArray getBezierCurves() { - return mD.getBezierCurves(); - } } diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 58535c4b2..759e854a8 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -184,13 +184,6 @@ static class PathParser { mString = d; } - ReadableArray getBezierCurves() { - if (mBezierCurves == null) { - getPath(); - } - return mBezierCurves; - } - private void executeCommand(String command) { switch (command) { // moveTo command diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index 36e043d2c..a5df934d9 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -59,7 +59,7 @@ public String getSpacing() { return mSpacing; } - public String getStartOffset() { + String getStartOffset() { return mStartOffset; } diff --git a/android/src/main/java/com/horcrux/svg/UseShadowNode.java b/android/src/main/java/com/horcrux/svg/UseShadowNode.java index b9bbf62bc..8eced936a 100644 --- a/android/src/main/java/com/horcrux/svg/UseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/UseShadowNode.java @@ -44,14 +44,6 @@ public void setHeight(String height) { markUpdated(); } - public String getWidth() { - return mWidth; - } - - public String getHeight() { - return mHeight; - } - @Override public void draw(Canvas canvas, Paint paint, float opacity) { VirtualNode template = getSvgShadowNode().getDefinedTemplate(mHref); diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 900030d4b..c742aefd8 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -41,8 +41,6 @@ abstract class VirtualNode extends LayoutShadowNode { private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; float mOpacity = 1f; - private float mScaleX = 1f; - private float mScaleY = 1f; private double mFontSize = -1; private double mParentFontSize = -1; Matrix mMatrix = new Matrix(); @@ -213,18 +211,6 @@ public void setOpacity(float opacity) { markUpdated(); } - @ReactProp(name = "scaleX", defaultFloat = 1f) - public void setScaleX(float scaleX) { - mScaleX = scaleX; - markUpdated(); - } - - @ReactProp(name = "scaleY", defaultFloat = 1f) - public void setScaleY(float scaleY) { - mScaleY = scaleY; - markUpdated(); - } - @ReactProp(name = "matrix") public void setMatrix(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { @@ -250,18 +236,6 @@ public void setMatrix(@Nullable ReadableArray matrixArray) { markUpdated(); } - public Matrix getMatrix() { - return mMatrix; - } - - public float getScaleX() { - return mScaleX; - } - - public float getScaleY() { - return mScaleY; - } - @ReactProp(name = "responsible") public void setResponsible(boolean responsible) { mResponsible = responsible; From bb6ee3e430d3e491887fcfd5832ebccb9499dd8c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 02:13:50 +0300 Subject: [PATCH 069/198] Simplify naming. --- .../src/main/java/com/horcrux/svg/Brush.java | 2 +- .../java/com/horcrux/svg/GlyphContext.java | 34 ++++++++----------- .../java/com/horcrux/svg/TSpanShadowNode.java | 8 ++--- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/Brush.java b/android/src/main/java/com/horcrux/svg/Brush.java index b641b3f75..eff89a95d 100644 --- a/android/src/main/java/com/horcrux/svg/Brush.java +++ b/android/src/main/java/com/horcrux/svg/Brush.java @@ -24,8 +24,8 @@ class Brush { private BrushType mType = BrushType.LINEAR_GRADIENT; private final ReadableArray mPoints; private ReadableArray mColors; - private Matrix mMatrix; private final boolean mUseObjectBoundingBox; + private Matrix mMatrix; private Rect mUserSpaceBoundingBox; Brush(BrushType type, ReadableArray points, BrushUnits units) { diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 93447cd62..b4bfeeaf1 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -123,16 +123,16 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read if (positionX != null) { mXPositionsIndex++; mXPositionIndex = -1; - mXs = positionX.trim().split("\\s+"); mXPositionIndices.add(mXPositionIndex); + mXs = positionX.trim().split("\\s+"); mXPositionsContext.add(mXs); } if (positionY != null) { mYPositionsIndex++; mYPositionIndex = -1; - mYs = positionY.trim().split("\\s+"); mYPositionIndices.add(mYPositionIndex); + mYs = positionY.trim().split("\\s+"); mYPositionsContext.add(mYs); } @@ -239,34 +239,30 @@ void popContext() { } } - PointF getNextGlyphPoint(float glyphWidth) { - setGlyphPositionX(); - setGlyphPositionY(); + PointF nextPoint(float glyphWidth) { + nextPositionX(); + nextPositionY(); mCurrentPosition.x += glyphWidth; return mCurrentPosition; } - PointF getNextGlyphDelta() { - setNextDeltaX(); - setNextDeltaY(); + PointF nextDelta() { + nextDeltaX(); + nextDeltaY(); return mCurrentDelta; } - float getNextGlyphRotation() { - setNextGlyphRotation(); - return mRotations[mRotationIndex]; - } - - private void setNextGlyphRotation() { + float nextRotation() { for (int index = mRotationsIndex; index >= 0; index--) { int rotationIndex = mRotationIndices.get(index); mRotationIndices.set(index, rotationIndex + 1); } mRotationIndex = Math.min(mRotationIndex + 1, mRotations.length - 1); + return mRotations[mRotationIndex]; } - private void setNextDeltaX() { + private void nextDeltaX() { for (int index = mDeltaXsIndex; index >= 0; index--) { int deltaIndex = mDeltaXIndices.get(index); mDeltaXIndices.set(index, deltaIndex + 1); @@ -280,7 +276,7 @@ private void setNextDeltaX() { } } - private void setNextDeltaY() { + private void nextDeltaY() { for (int index = mDeltaYsIndex; index >= 0; index--) { int deltaIndex = mDeltaYIndices.get(index); mDeltaYIndices.set(index, deltaIndex + 1); @@ -294,7 +290,7 @@ private void setNextDeltaY() { } } - private void setGlyphPositionX() { + private void nextPositionX() { for (int index = mXPositionsIndex; index >= 0; index--) { int positionIndex = mXPositionIndices.get(index); mXPositionIndices.set(index, positionIndex + 1); @@ -309,7 +305,7 @@ private void setGlyphPositionX() { } } - private void setGlyphPositionY() { + private void nextPositionY() { for (int index = mYPositionsIndex; index >= 0; index--) { int positionIndex = mYPositionIndices.get(index); mYPositionIndices.set(index, positionIndex + 1); @@ -348,7 +344,7 @@ float getHeight() { return DEFAULT_FONT_SIZE; } - ReadableMap getGlyphFont() { + ReadableMap getFont() { float letterSpacing = DEFAULT_LETTER_SPACING; float kerning = DEFAULT_KERNING; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 01254d40d..b9923e001 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -112,7 +112,7 @@ private Path getLinePath(String line, Paint paint) { } GlyphContext gc = getTextRootGlyphContext(); - ReadableMap font = gc.getGlyphFont(); + ReadableMap font = gc.getFont(); applyTextPropertiesToPaint(paint, font); float distance = 0; @@ -160,9 +160,9 @@ private Path getLinePath(String line, Paint paint) { previous = current; } - point = gc.getNextGlyphPoint(width + kerning); - rotation = gc.getNextGlyphRotation(); - delta = gc.getNextGlyphDelta(); + point = gc.nextPoint(width + kerning); + rotation = gc.nextRotation(); + delta = gc.nextDelta(); matrix = new Matrix(); float x = offset + point.x + delta.x - width; From b7bfaab63dd12f7f8dbf503416dba4449d7531d4 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 03:00:15 +0300 Subject: [PATCH 070/198] Cache textRoot for getTextRootGlyphContext. Use getTextRootGlyphContext() instead of getTextRoot().getGlyphContext() in TextShadowNode.pushGlyphContext() --- android/src/main/java/com/horcrux/svg/GroupShadowNode.java | 6 +++++- android/src/main/java/com/horcrux/svg/TextShadowNode.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 72173c3bc..d26d485b6 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -29,6 +29,7 @@ class GroupShadowNode extends RenderableShadowNode { @Nullable ReadableMap mFont; private GlyphContext mGlyphContext; + private GroupShadowNode textRoot; @ReactProp(name = "font") public void setFont(@Nullable ReadableMap font) { @@ -47,7 +48,10 @@ GlyphContext getGlyphContext() { } GlyphContext getTextRootGlyphContext() { - return getTextRoot().getGlyphContext(); + if (textRoot == null) { + textRoot = getTextRoot(); + } + return textRoot.getGlyphContext(); } void pushGlyphContext() { diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 1bf6446fb..e0ff8a41b 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -178,6 +178,6 @@ Path getGroupPath(Canvas canvas, Paint paint) { @Override void pushGlyphContext() { boolean isTextNode = !(this instanceof TextPathShadowNode) && !(this instanceof TSpanShadowNode); - getTextRoot().getGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY, isTextNode); + getTextRootGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY, isTextNode); } } From 7994070c41faf0f7ed1f1ea6de46a54b673946f3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 03:13:57 +0300 Subject: [PATCH 071/198] Upgrade com.android.tools.build:gradle to 2.3.2 and buildToolsVersion to 26 --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index f1ecfd90f..cc8cda597 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.3.2' } } @@ -12,7 +12,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 26 - buildToolsVersion "25.0.3" + buildToolsVersion '26' defaultConfig { minSdkVersion 16 From 92714207a6cb6d79138978e40f4737e841c757f5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 03:28:34 +0300 Subject: [PATCH 072/198] Simplify getFont --- .../java/com/horcrux/svg/GlyphContext.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index b4bfeeaf1..e99d2cb7c 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -23,7 +23,6 @@ class GlyphContext { static final float DEFAULT_FONT_SIZE = 12f; - private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_LETTER_SPACING = 0f; // Unique input attribute lists (only added if node sets a value) @@ -345,56 +344,57 @@ float getHeight() { } ReadableMap getFont() { - float letterSpacing = DEFAULT_LETTER_SPACING; - float kerning = DEFAULT_KERNING; + WritableMap map = Arguments.createMap(); + + map.putDouble("letterSpacing", DEFAULT_LETTER_SPACING); + map.putBoolean("isKerningValueSet", false); + map.putDouble("fontSize", fontSize); boolean letterSpacingSet = false; + boolean fontFamilySet = false; + boolean fontWeightSet = false; + boolean fontStyleSet = false; boolean kerningSet = false; - String fontFamily = null; - String fontWeight = null; - String fontStyle = null; - for (int index = top; index >= 0; index--) { ReadableMap font = mFontContext.get(index); - if (fontFamily == null && font.hasKey("fontFamily")) { - fontFamily = font.getString("fontFamily"); + if (!fontFamilySet && font.hasKey("fontFamily")) { + String fontFamily = font.getString("fontFamily"); + map.putString("fontFamily", fontFamily); + fontFamilySet = true; } if (!kerningSet && font.hasKey("kerning")) { - kerning = Float.valueOf(font.getString("kerning")); + float kerning = Float.valueOf(font.getString("kerning")); + map.putBoolean("isKerningValueSet", true); + map.putDouble("kerning", kerning); kerningSet = true; } if (!letterSpacingSet && font.hasKey("letterSpacing")) { - letterSpacing = Float.valueOf(font.getString("letterSpacing")); + float letterSpacing = Float.valueOf(font.getString("letterSpacing")); + map.putDouble("letterSpacing", letterSpacing); letterSpacingSet = true; } - if (fontWeight == null && font.hasKey("fontWeight")) { - fontWeight = font.getString("fontWeight"); + if (!fontWeightSet && font.hasKey("fontWeight")) { + String fontWeight = font.getString("fontWeight"); + map.putString("fontWeight", fontWeight); + fontWeightSet = true; } - if (fontStyle == null && font.hasKey("fontStyle")) { - fontStyle = font.getString("fontStyle"); + if (!fontStyleSet && font.hasKey("fontStyle")) { + String fontStyle = font.getString("fontStyle"); + map.putString("fontStyle", fontStyle); + fontStyleSet = true; } - if (fontFamily != null && kerningSet && letterSpacingSet && fontWeight != null && fontStyle != null) { + if (fontFamilySet && kerningSet && letterSpacingSet && fontWeightSet && fontStyleSet) { break; } } - WritableMap map = Arguments.createMap(); - - map.putBoolean("isKerningValueSet", kerningSet); - map.putDouble("letterSpacing", letterSpacing); - map.putString("fontFamily", fontFamily); - map.putString("fontWeight", fontWeight); - map.putString("fontStyle", fontStyle); - map.putDouble("fontSize", fontSize); - map.putDouble("kerning", kerning); - return map; } From ac46b68f70386a5332bc7b5207d344bc41204f32 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 03:35:09 +0300 Subject: [PATCH 073/198] Restore default kerning, remove redundant reset. --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index e99d2cb7c..194237909 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -23,6 +23,7 @@ class GlyphContext { static final float DEFAULT_FONT_SIZE = 12f; + private static final float DEFAULT_KERNING = 0f; private static final float DEFAULT_LETTER_SPACING = 0f; // Unique input attribute lists (only added if node sets a value) @@ -201,10 +202,6 @@ void popContext() { int dx = mDeltaXsIndex; int dy = mDeltaYsIndex; - if (mContextLength == 0) { - reset(); - } - mXPositionsIndex = mXPositionsIndices.get(mContextLength); mYPositionsIndex = mYPositionsIndices.get(mContextLength); mRotationsIndex = mRotationsIndices.get(mContextLength); @@ -348,6 +345,7 @@ ReadableMap getFont() { map.putDouble("letterSpacing", DEFAULT_LETTER_SPACING); map.putBoolean("isKerningValueSet", false); + map.putDouble("kerning", DEFAULT_KERNING); map.putDouble("fontSize", fontSize); boolean letterSpacingSet = false; From 5c885fe35e773bdeed6024c152a525db8f346ebb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 04:37:03 +0300 Subject: [PATCH 074/198] Remove use of PointF, use plain float. --- .../java/com/horcrux/svg/GlyphContext.java | 107 +++++++++--------- .../java/com/horcrux/svg/TSpanShadowNode.java | 37 +++--- 2 files changed, 76 insertions(+), 68 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 194237909..52346a67c 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -9,8 +9,6 @@ package com.horcrux.svg; -import android.graphics.PointF; - import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -18,7 +16,6 @@ import java.util.ArrayList; -import javax.annotation.Nonnull; import javax.annotation.Nullable; class GlyphContext { @@ -51,11 +48,16 @@ class GlyphContext { private final ArrayList mFontContext = new ArrayList<>(); private final ArrayList mNodes = new ArrayList<>(); - // Current accumulated values - private @Nonnull final PointF mCurrentPosition = new PointF(); - private @Nonnull final PointF mCurrentDelta = new PointF(); + // Cached per push context private double fontSize = DEFAULT_FONT_SIZE; + // Current accumulated values + private float mx; + private float my; + private float mr; + private float mdx; + private float mdy; + // Current attribute list private float[] mRotations = new float[]{0}; private float[] mDeltaXs = new float[]{}; @@ -77,6 +79,7 @@ class GlyphContext { private int mDeltaXIndex = -1; private int mDeltaYIndex = -1; + // Stack length and last index private int mContextLength; private int top = -1; @@ -180,7 +183,7 @@ private void pushIndices() { private void reset() { mXPositionsIndex = mYPositionsIndex = mRotationsIndex = mDeltaXsIndex = mDeltaYsIndex = 0; mXPositionIndex = mYPositionIndex = mRotationIndex = mDeltaXIndex = mDeltaYIndex = -1; - mCurrentPosition.x = mCurrentPosition.y = mCurrentDelta.x = mCurrentDelta.y = 0; + mx = my = mr = mdx = mdy = 0; } void popContext() { @@ -235,17 +238,40 @@ void popContext() { } } - PointF nextPoint(float glyphWidth) { - nextPositionX(); - nextPositionY(); - mCurrentPosition.x += glyphWidth; - return mCurrentPosition; + float nextX(float glyphWidth) { + for (int index = mXPositionsIndex; index >= 0; index--) { + int positionIndex = mXPositionIndices.get(index); + mXPositionIndices.set(index, positionIndex + 1); + } + + int nextIndex = mXPositionIndex + 1; + if (nextIndex < mXs.length) { + mdx = 0; + mXPositionIndex = nextIndex; + String val = mXs[nextIndex]; + mx = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, fontSize); + } + + mx += glyphWidth; + + return mx; } - PointF nextDelta() { - nextDeltaX(); - nextDeltaY(); - return mCurrentDelta; + float nextY() { + for (int index = mYPositionsIndex; index >= 0; index--) { + int positionIndex = mYPositionIndices.get(index); + mYPositionIndices.set(index, positionIndex + 1); + } + + int nextIndex = mYPositionIndex + 1; + if (nextIndex < mYs.length) { + mdy = 0; + mYPositionIndex = nextIndex; + String val = mYs[nextIndex]; + my = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, fontSize); + } + + return my; } float nextRotation() { @@ -254,11 +280,16 @@ float nextRotation() { mRotationIndices.set(index, rotationIndex + 1); } - mRotationIndex = Math.min(mRotationIndex + 1, mRotations.length - 1); - return mRotations[mRotationIndex]; + int nextIndex = mRotationIndex + 1; + if (nextIndex < mRotations.length) { + mRotationIndex = Math.min(mRotationIndex + 1, mRotations.length - 1); + mr = mRotations[mRotationIndex]; + } + + return mr; } - private void nextDeltaX() { + float nextDeltaX() { for (int index = mDeltaXsIndex; index >= 0; index--) { int deltaIndex = mDeltaXIndices.get(index); mDeltaXIndices.set(index, deltaIndex + 1); @@ -268,11 +299,13 @@ private void nextDeltaX() { if (nextIndex < mDeltaXs.length) { mDeltaXIndex = nextIndex; float val = mDeltaXs[nextIndex]; - mCurrentDelta.x += val * mScale; + mdx += val * mScale; } + + return mdx; } - private void nextDeltaY() { + float nextDeltaY() { for (int index = mDeltaYsIndex; index >= 0; index--) { int deltaIndex = mDeltaYIndices.get(index); mDeltaYIndices.set(index, deltaIndex + 1); @@ -282,38 +315,10 @@ private void nextDeltaY() { if (nextIndex < mDeltaYs.length) { mDeltaYIndex = nextIndex; float val = mDeltaYs[nextIndex]; - mCurrentDelta.y += val * mScale; - } - } - - private void nextPositionX() { - for (int index = mXPositionsIndex; index >= 0; index--) { - int positionIndex = mXPositionIndices.get(index); - mXPositionIndices.set(index, positionIndex + 1); - } - - int nextIndex = mXPositionIndex + 1; - if (nextIndex < mXs.length) { - mCurrentDelta.x = 0; - mXPositionIndex = nextIndex; - String val = mXs[nextIndex]; - mCurrentPosition.x = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, fontSize); + mdy += val * mScale; } - } - private void nextPositionY() { - for (int index = mYPositionsIndex; index >= 0; index--) { - int positionIndex = mYPositionIndices.get(index); - mYPositionIndices.set(index, positionIndex + 1); - } - - int nextIndex = mYPositionIndex + 1; - if (nextIndex < mYs.length) { - mCurrentDelta.y = 0; - mYPositionIndex = nextIndex; - String val = mYs[nextIndex]; - mCurrentPosition.y = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, fontSize); - } + return mdy; } float getWidth() { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index b9923e001..58c72b02b 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -16,7 +16,6 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; -import android.graphics.PointF; import android.graphics.RectF; import android.graphics.Typeface; @@ -80,12 +79,12 @@ protected Path getPath(Canvas canvas, Paint paint) { setupTextPath(); pushGlyphContext(); - Path path = mCache = getLinePath(mContent, paint); + mCache = getLinePath(mContent, paint); popGlyphContext(); - path.computeBounds(new RectF(), true); + mCache.computeBounds(new RectF(), true); - return path; + return mCache; } private float getTextAnchorShift(float width) { @@ -134,13 +133,16 @@ private Path getLinePath(String line, Paint paint) { offset += getTextAnchorShift(textMeasure); + float x; + float y; + float r; + float dx; + float dy; + Path glyph; float width; - PointF point; - PointF delta; Matrix matrix; String current; - float rotation; String previous = ""; float previousWidth = 0; char[] chars = line.toCharArray(); @@ -160,16 +162,18 @@ private Path getLinePath(String line, Paint paint) { previous = current; } - point = gc.nextPoint(width + kerning); - rotation = gc.nextRotation(); - delta = gc.nextDelta(); + x = gc.nextX(width + kerning); + y = gc.nextY(); + r = gc.nextRotation(); + dx = gc.nextDeltaX(); + dy = gc.nextDeltaY(); matrix = new Matrix(); - float x = offset + point.x + delta.x - width; + float start = offset + x + dx - width; if (textPath != null) { float halfway = width / 2; - float midpoint = x + halfway; + float midpoint = start + halfway; if (midpoint > distance) { break; @@ -180,15 +184,14 @@ private Path getLinePath(String line, Paint paint) { assert pm != null; pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - matrix.preTranslate(-halfway, delta.y); + matrix.preTranslate(-halfway, dy); matrix.preScale(renderMethodScaling, 1); - matrix.postTranslate(0, point.y); + matrix.postTranslate(0, y); } else { - float y = point.y + delta.y; - matrix.setTranslate(x, y); + matrix.setTranslate(start, y + dy); } - matrix.preRotate(rotation); + matrix.preRotate(r); glyph.transform(matrix); path.addPath(glyph); } From 93d507b029609798a95e79cb9e19940b1056f8ab Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 04:59:05 +0300 Subject: [PATCH 075/198] Simplify / Optimize nextRotation --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 8 ++++---- .../src/main/java/com/horcrux/svg/TSpanShadowNode.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 52346a67c..124b75196 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -59,11 +59,11 @@ class GlyphContext { private float mdy; // Current attribute list + private String[] mXs = new String[]{}; + private String[] mYs = new String[]{}; private float[] mRotations = new float[]{0}; private float[] mDeltaXs = new float[]{}; private float[] mDeltaYs = new float[]{}; - private String[] mXs = new String[]{}; - private String[] mYs = new String[]{}; // Current attribute list index private int mXPositionsIndex; @@ -282,8 +282,8 @@ float nextRotation() { int nextIndex = mRotationIndex + 1; if (nextIndex < mRotations.length) { - mRotationIndex = Math.min(mRotationIndex + 1, mRotations.length - 1); - mr = mRotations[mRotationIndex]; + mRotationIndex = nextIndex; + mr = mRotations[nextIndex]; } return mr; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 58c72b02b..5a01f540c 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -114,8 +114,8 @@ private Path getLinePath(String line, Paint paint) { ReadableMap font = gc.getFont(); applyTextPropertiesToPaint(paint, font); - float distance = 0; float offset = 0; + float distance = 0; float renderMethodScaling = 1; float textMeasure = paint.measureText(line); From a103cef1a62c2d378e09c872fcf4c200ee66a134 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 05:31:32 +0300 Subject: [PATCH 076/198] Simplify naming --- .../java/com/horcrux/svg/GlyphContext.java | 299 +++++++++--------- .../java/com/horcrux/svg/TextShadowNode.java | 2 +- 2 files changed, 155 insertions(+), 146 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 124b75196..4dab211df 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -24,25 +24,25 @@ class GlyphContext { private static final float DEFAULT_LETTER_SPACING = 0f; // Unique input attribute lists (only added if node sets a value) - private final ArrayList mXPositionsContext = new ArrayList<>(); - private final ArrayList mYPositionsContext = new ArrayList<>(); - private final ArrayList mRotationsContext = new ArrayList<>(); - private final ArrayList mDeltaXsContext = new ArrayList<>(); - private final ArrayList mDeltaYsContext = new ArrayList<>(); + private final ArrayList mXsContext = new ArrayList<>(); + private final ArrayList mYsContext = new ArrayList<>(); + private final ArrayList mRsContext = new ArrayList<>(); + private final ArrayList mdXsContext = new ArrayList<>(); + private final ArrayList mdYsContext = new ArrayList<>(); // Unique index into attribute list (one per unique list) - private final ArrayList mXPositionIndices = new ArrayList<>(); - private final ArrayList mYPositionIndices = new ArrayList<>(); - private final ArrayList mRotationIndices = new ArrayList<>(); - private final ArrayList mDeltaXIndices = new ArrayList<>(); - private final ArrayList mDeltaYIndices = new ArrayList<>(); + private final ArrayList mXIndices = new ArrayList<>(); + private final ArrayList mYIndices = new ArrayList<>(); + private final ArrayList mRIndices = new ArrayList<>(); + private final ArrayList mdXIndices = new ArrayList<>(); + private final ArrayList mdYIndices = new ArrayList<>(); // Index of unique context used (one per node push/pop) - private final ArrayList mXPositionsIndices = new ArrayList<>(); - private final ArrayList mYPositionsIndices = new ArrayList<>(); - private final ArrayList mRotationsIndices = new ArrayList<>(); - private final ArrayList mDeltaXsIndices = new ArrayList<>(); - private final ArrayList mDeltaYsIndices = new ArrayList<>(); + private final ArrayList mXsIndices = new ArrayList<>(); + private final ArrayList mYsIndices = new ArrayList<>(); + private final ArrayList mRsIndices = new ArrayList<>(); + private final ArrayList mdXsIndices = new ArrayList<>(); + private final ArrayList mdYsIndices = new ArrayList<>(); // Current stack (one per node push/pop) private final ArrayList mFontContext = new ArrayList<>(); @@ -61,23 +61,23 @@ class GlyphContext { // Current attribute list private String[] mXs = new String[]{}; private String[] mYs = new String[]{}; - private float[] mRotations = new float[]{0}; - private float[] mDeltaXs = new float[]{}; - private float[] mDeltaYs = new float[]{}; + private float[] mRs = new float[]{0}; + private float[] mdXs = new float[]{}; + private float[] mdYs = new float[]{}; // Current attribute list index - private int mXPositionsIndex; - private int mYPositionsIndex; - private int mRotationsIndex; - private int mDeltaXsIndex; - private int mDeltaYsIndex; + private int mXsIndex; + private int mYsIndex; + private int mRsIndex; + private int mdXsIndex; + private int mdYsIndex; // Current value index in current attribute list - private int mXPositionIndex = -1; - private int mYPositionIndex = -1; - private int mRotationIndex = -1; - private int mDeltaXIndex = -1; - private int mDeltaYIndex = -1; + private int mXIndex = -1; + private int mYIndex = -1; + private int mRIndex = -1; + private int mdXIndex = -1; + private int mdYIndex = -1; // Stack length and last index private int mContextLength; @@ -93,17 +93,17 @@ class GlyphContext { mWidth = width; mHeight = height; - mXPositionsContext.add(mXs); - mYPositionsContext.add(mYs); - mDeltaXsContext.add(mDeltaXs); - mDeltaYsContext.add(mDeltaYs); - mRotationsContext.add(mRotations); + mXsContext.add(mXs); + mYsContext.add(mYs); + mdXsContext.add(mdXs); + mdYsContext.add(mdYs); + mRsContext.add(mRs); - mXPositionIndices.add(mXPositionIndex); - mYPositionIndices.add(mYPositionIndex); - mRotationIndices.add(mRotationIndex); - mDeltaXIndices.add(mDeltaXIndex); - mDeltaYIndices.add(mDeltaYIndex); + mXIndices.add(mXIndex); + mYIndices.add(mYIndex); + mRIndices.add(mRIndex); + mdXIndices.add(mdXIndex); + mdYIndices.add(mdYIndex); pushIndices(); } @@ -118,49 +118,58 @@ void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { fontSize = getFontSize(); } - void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY, @Nullable String positionX, @Nullable String positionY, boolean resetPosition) { - if (resetPosition) { - reset(); + void pushContext( + TextShadowNode node, + @Nullable ReadableMap font, + boolean reset, + @Nullable String x, + @Nullable String y, + @Nullable ReadableArray rotate, + @Nullable ReadableArray deltaX, + @Nullable ReadableArray deltaY + ) { + if (reset) { + this.reset(); } - if (positionX != null) { - mXPositionsIndex++; - mXPositionIndex = -1; - mXPositionIndices.add(mXPositionIndex); - mXs = positionX.trim().split("\\s+"); - mXPositionsContext.add(mXs); + if (x != null) { + mXsIndex++; + mXIndex = -1; + mXIndices.add(mXIndex); + mXs = x.trim().split("\\s+"); + mXsContext.add(mXs); } - if (positionY != null) { - mYPositionsIndex++; - mYPositionIndex = -1; - mYPositionIndices.add(mYPositionIndex); - mYs = positionY.trim().split("\\s+"); - mYPositionsContext.add(mYs); + if (y != null) { + mYsIndex++; + mYIndex = -1; + mYIndices.add(mYIndex); + mYs = y.trim().split("\\s+"); + mYsContext.add(mYs); } if (rotate != null && rotate.size() != 0) { - mRotationsIndex++; - mRotationIndex = -1; - mRotationIndices.add(mRotationIndex); - mRotations = getFloatArrayFromReadableArray(rotate); - mRotationsContext.add(mRotations); + mRsIndex++; + mRIndex = -1; + mRIndices.add(mRIndex); + mRs = getFloatArrayFromReadableArray(rotate); + mRsContext.add(mRs); } if (deltaX != null && deltaX.size() != 0) { - mDeltaXsIndex++; - mDeltaXIndex = -1; - mDeltaXIndices.add(mDeltaXIndex); - mDeltaXs = getFloatArrayFromReadableArray(deltaX); - mDeltaXsContext.add(mDeltaXs); + mdXsIndex++; + mdXIndex = -1; + mdXIndices.add(mdXIndex); + mdXs = getFloatArrayFromReadableArray(deltaX); + mdXsContext.add(mdXs); } if (deltaY != null && deltaY.size() != 0) { - mDeltaYsIndex++; - mDeltaYIndex = -1; - mDeltaYIndices.add(mDeltaYIndex); - mDeltaYs = getFloatArrayFromReadableArray(deltaY); - mDeltaYsContext.add(mDeltaYs); + mdYsIndex++; + mdYIndex = -1; + mdYIndices.add(mdYIndex); + mdYs = getFloatArrayFromReadableArray(deltaY); + mdYsContext.add(mdYs); } mFontContext.add(font); @@ -173,16 +182,16 @@ void pushContext(TextShadowNode node, @Nullable ReadableMap font, @Nullable Read } private void pushIndices() { - mXPositionsIndices.add(mXPositionsIndex); - mYPositionsIndices.add(mYPositionsIndex); - mRotationsIndices.add(mRotationsIndex); - mDeltaXsIndices.add(mDeltaXsIndex); - mDeltaYsIndices.add(mDeltaYsIndex); + mXsIndices.add(mXsIndex); + mYsIndices.add(mYsIndex); + mRsIndices.add(mRsIndex); + mdXsIndices.add(mdXsIndex); + mdYsIndices.add(mdYsIndex); } private void reset() { - mXPositionsIndex = mYPositionsIndex = mRotationsIndex = mDeltaXsIndex = mDeltaYsIndex = 0; - mXPositionIndex = mYPositionIndex = mRotationIndex = mDeltaXIndex = mDeltaYIndex = -1; + mXsIndex = mYsIndex = mRsIndex = mdXsIndex = mdYsIndex = 0; + mXIndex = mYIndex = mRIndex = mdXIndex = mdYIndex = -1; mx = my = mr = mdx = mdy = 0; } @@ -190,64 +199,64 @@ void popContext() { mContextLength--; top--; - mFontContext.remove(mContextLength); mNodes.remove(mContextLength); + mFontContext.remove(mContextLength); - mXPositionsIndices.remove(mContextLength); - mYPositionsIndices.remove(mContextLength); - mRotationsIndices.remove(mContextLength); - mDeltaXsIndices.remove(mContextLength); - mDeltaYsIndices.remove(mContextLength); - - int x = mXPositionsIndex; - int y = mYPositionsIndex; - int r = mRotationsIndex; - int dx = mDeltaXsIndex; - int dy = mDeltaYsIndex; - - mXPositionsIndex = mXPositionsIndices.get(mContextLength); - mYPositionsIndex = mYPositionsIndices.get(mContextLength); - mRotationsIndex = mRotationsIndices.get(mContextLength); - mDeltaXsIndex = mDeltaXsIndices.get(mContextLength); - mDeltaYsIndex = mDeltaYsIndices.get(mContextLength); - - if (x != mXPositionsIndex) { - mXPositionsContext.remove(x); - mXs = mXPositionsContext.get(mXPositionsIndex); - mXPositionIndex = mXPositionIndices.get(mXPositionsIndex); + mXsIndices.remove(mContextLength); + mYsIndices.remove(mContextLength); + mRsIndices.remove(mContextLength); + mdXsIndices.remove(mContextLength); + mdYsIndices.remove(mContextLength); + + int x = mXsIndex; + int y = mYsIndex; + int r = mRsIndex; + int dx = mdXsIndex; + int dy = mdYsIndex; + + mXsIndex = mXsIndices.get(mContextLength); + mYsIndex = mYsIndices.get(mContextLength); + mRsIndex = mRsIndices.get(mContextLength); + mdXsIndex = mdXsIndices.get(mContextLength); + mdYsIndex = mdYsIndices.get(mContextLength); + + if (x != mXsIndex) { + mXsContext.remove(x); + mXs = mXsContext.get(mXsIndex); + mXIndex = mXIndices.get(mXsIndex); } - if (y != mYPositionsIndex) { - mYPositionsContext.remove(y); - mYs = mYPositionsContext.get(mYPositionsIndex); - mYPositionIndex = mYPositionIndices.get(mYPositionsIndex); + if (y != mYsIndex) { + mYsContext.remove(y); + mYs = mYsContext.get(mYsIndex); + mYIndex = mYIndices.get(mYsIndex); } - if (r != mRotationsIndex) { - mRotationsContext.remove(r); - mRotations = mRotationsContext.get(mRotationsIndex); - mRotationIndex = mRotationIndices.get(mRotationsIndex); + if (r != mRsIndex) { + mRsContext.remove(r); + mRs = mRsContext.get(mRsIndex); + mRIndex = mRIndices.get(mRsIndex); } - if (dx != mDeltaXsIndex) { - mDeltaXsContext.remove(dx); - mDeltaXs = mDeltaXsContext.get(mDeltaXsIndex); - mDeltaXIndex = mDeltaXIndices.get(mDeltaXsIndex); + if (dx != mdXsIndex) { + mdXsContext.remove(dx); + mdXs = mdXsContext.get(mdXsIndex); + mdXIndex = mdXIndices.get(mdXsIndex); } - if (dy != mDeltaYsIndex) { - mDeltaYsContext.remove(dy); - mDeltaYs = mDeltaYsContext.get(mDeltaYsIndex); - mDeltaYIndex = mDeltaYIndices.get(mDeltaYsIndex); + if (dy != mdYsIndex) { + mdYsContext.remove(dy); + mdYs = mdYsContext.get(mdYsIndex); + mdYIndex = mdYIndices.get(mdYsIndex); } } float nextX(float glyphWidth) { - for (int index = mXPositionsIndex; index >= 0; index--) { - int positionIndex = mXPositionIndices.get(index); - mXPositionIndices.set(index, positionIndex + 1); + for (int index = mXsIndex; index >= 0; index--) { + int Index = mXIndices.get(index); + mXIndices.set(index, Index + 1); } - int nextIndex = mXPositionIndex + 1; + int nextIndex = mXIndex + 1; if (nextIndex < mXs.length) { mdx = 0; - mXPositionIndex = nextIndex; + mXIndex = nextIndex; String val = mXs[nextIndex]; mx = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, fontSize); } @@ -258,15 +267,15 @@ float nextX(float glyphWidth) { } float nextY() { - for (int index = mYPositionsIndex; index >= 0; index--) { - int positionIndex = mYPositionIndices.get(index); - mYPositionIndices.set(index, positionIndex + 1); + for (int index = mYsIndex; index >= 0; index--) { + int Index = mYIndices.get(index); + mYIndices.set(index, Index + 1); } - int nextIndex = mYPositionIndex + 1; + int nextIndex = mYIndex + 1; if (nextIndex < mYs.length) { mdy = 0; - mYPositionIndex = nextIndex; + mYIndex = nextIndex; String val = mYs[nextIndex]; my = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, fontSize); } @@ -275,30 +284,30 @@ float nextY() { } float nextRotation() { - for (int index = mRotationsIndex; index >= 0; index--) { - int rotationIndex = mRotationIndices.get(index); - mRotationIndices.set(index, rotationIndex + 1); + for (int index = mRsIndex; index >= 0; index--) { + int rotationIndex = mRIndices.get(index); + mRIndices.set(index, rotationIndex + 1); } - int nextIndex = mRotationIndex + 1; - if (nextIndex < mRotations.length) { - mRotationIndex = nextIndex; - mr = mRotations[nextIndex]; + int nextIndex = mRIndex + 1; + if (nextIndex < mRs.length) { + mRIndex = nextIndex; + mr = mRs[nextIndex]; } return mr; } float nextDeltaX() { - for (int index = mDeltaXsIndex; index >= 0; index--) { - int deltaIndex = mDeltaXIndices.get(index); - mDeltaXIndices.set(index, deltaIndex + 1); + for (int index = mdXsIndex; index >= 0; index--) { + int deltaIndex = mdXIndices.get(index); + mdXIndices.set(index, deltaIndex + 1); } - int nextIndex = mDeltaXIndex + 1; - if (nextIndex < mDeltaXs.length) { - mDeltaXIndex = nextIndex; - float val = mDeltaXs[nextIndex]; + int nextIndex = mdXIndex + 1; + if (nextIndex < mdXs.length) { + mdXIndex = nextIndex; + float val = mdXs[nextIndex]; mdx += val * mScale; } @@ -306,15 +315,15 @@ float nextDeltaX() { } float nextDeltaY() { - for (int index = mDeltaYsIndex; index >= 0; index--) { - int deltaIndex = mDeltaYIndices.get(index); - mDeltaYIndices.set(index, deltaIndex + 1); + for (int index = mdYsIndex; index >= 0; index--) { + int deltaIndex = mdYIndices.get(index); + mdYIndices.set(index, deltaIndex + 1); } - int nextIndex = mDeltaYIndex + 1; - if (nextIndex < mDeltaYs.length) { - mDeltaYIndex = nextIndex; - float val = mDeltaYs[nextIndex]; + int nextIndex = mdYIndex + 1; + if (nextIndex < mdYs.length) { + mdYIndex = nextIndex; + float val = mdYs[nextIndex]; mdy += val * mScale; } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index e0ff8a41b..63dd61699 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -178,6 +178,6 @@ Path getGroupPath(Canvas canvas, Paint paint) { @Override void pushGlyphContext() { boolean isTextNode = !(this instanceof TextPathShadowNode) && !(this instanceof TSpanShadowNode); - getTextRootGlyphContext().pushContext(this, mFont, mRotate, mDeltaX, mDeltaY, mPositionX, mPositionY, isTextNode); + getTextRootGlyphContext().pushContext(this, mFont, isTextNode, mPositionX, mPositionY, mRotate, mDeltaX, mDeltaY); } } From ba7d9c6da893cf4bfff09bd8223392b9343f6a77 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 05:37:29 +0300 Subject: [PATCH 077/198] Refactor signature --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 2 +- android/src/main/java/com/horcrux/svg/TextShadowNode.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 4dab211df..107b4d733 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -119,9 +119,9 @@ void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { } void pushContext( + boolean reset, TextShadowNode node, @Nullable ReadableMap font, - boolean reset, @Nullable String x, @Nullable String y, @Nullable ReadableArray rotate, diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 63dd61699..74b60b96c 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -178,6 +178,6 @@ Path getGroupPath(Canvas canvas, Paint paint) { @Override void pushGlyphContext() { boolean isTextNode = !(this instanceof TextPathShadowNode) && !(this instanceof TSpanShadowNode); - getTextRootGlyphContext().pushContext(this, mFont, isTextNode, mPositionX, mPositionY, mRotate, mDeltaX, mDeltaY); + getTextRootGlyphContext().pushContext(isTextNode, this, mFont, mPositionX, mPositionY, mRotate, mDeltaX, mDeltaY); } } From 2e0d61bca5f2cdeb73c9f76ee1ed4c38b3535ebe Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 05:53:03 +0300 Subject: [PATCH 078/198] Fix popContext mContextLength usage. --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 107b4d733..21325b2eb 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -196,18 +196,18 @@ private void reset() { } void popContext() { - mContextLength--; - top--; - - mNodes.remove(mContextLength); - mFontContext.remove(mContextLength); - mXsIndices.remove(mContextLength); mYsIndices.remove(mContextLength); mRsIndices.remove(mContextLength); mdXsIndices.remove(mContextLength); mdYsIndices.remove(mContextLength); + mContextLength--; + top--; + + mNodes.remove(mContextLength); + mFontContext.remove(mContextLength); + int x = mXsIndex; int y = mYsIndex; int r = mRsIndex; From 4e99f2e9e1af78e95e22df6bf146b7ea513ec490 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 05:59:48 +0300 Subject: [PATCH 079/198] Refactor getFloatArrayFromReadableArray --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 21325b2eb..7e3a8f4a4 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -416,8 +416,10 @@ private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { for (int i = 0; i < size; i++) { switch (readableArray.getType(i)) { case String: + // em units String val = readableArray.getString(i); - floats[i] = (float) (Float.valueOf(val.substring(0, val.length() - 2)) * fontSize); + String substring = val.substring(0, val.length() - 2); + floats[i] = (float) (Float.valueOf(substring) * fontSize); break; case Number: From 2f80931f4d5be457c3fcee82ee993f6a0489892e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 06:04:32 +0300 Subject: [PATCH 080/198] Fix very long lines. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 5a01f540c..f52d83750 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -123,7 +123,9 @@ private Path getLinePath(String line, Paint paint) { if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - offset = PropHelper.fromRelativeToFloat(textPath.getStartOffset(), distance, 0, mScale, getFontSizeFromContext()); + double size = getFontSizeFromContext(); + String startOffset = textPath.getStartOffset(); + offset = PropHelper.fromRelativeToFloat(startOffset, distance, 0, mScale, size); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" String method = textPath.getMethod(); // method = "align | stretch" if ("stretch".equals(method)) { @@ -207,7 +209,7 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { paint.setTextSize(fontSize); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - paint.setLetterSpacing(letterSpacing / fontSize); // setLetterSpacing is only available from LOLLIPOP and on + paint.setLetterSpacing(letterSpacing / fontSize); } int decoration = getTextDecoration(); @@ -215,8 +217,10 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { paint.setUnderlineText(decoration == TEXT_DECORATION_UNDERLINE); paint.setStrikeThruText(decoration == TEXT_DECORATION_LINE_THROUGH); - 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)); + 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) { @@ -233,10 +237,12 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { Typeface tf = null; try { - tf = Typeface.createFromAsset(a, "fonts/" + font.getString(PROP_FONT_FAMILY) + ".otf"); + String path = "fonts/" + font.getString(PROP_FONT_FAMILY) + ".otf"; + tf = Typeface.createFromAsset(a, path); } catch (Exception ignored) { try { - tf = Typeface.createFromAsset(a, "fonts/" + font.getString(PROP_FONT_FAMILY) + ".ttf"); + String path = "fonts/" + font.getString(PROP_FONT_FAMILY) + ".ttf"; + tf = Typeface.createFromAsset(a, path); } catch (Exception ignored2) { try { tf = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle); From 431efe3f7b4b0be0e4fc1082023606677032eb83 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 06:32:16 +0300 Subject: [PATCH 081/198] Fix fontSize calculation for pushContext getFloatArrayFromReadableArray calls. --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 7e3a8f4a4..90530c61c 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -132,6 +132,13 @@ void pushContext( this.reset(); } + mFontContext.add(font); + mNodes.add(node); + mContextLength++; + top++; + + fontSize = getFontSize(); + if (x != null) { mXsIndex++; mXIndex = -1; @@ -172,13 +179,7 @@ void pushContext( mdYsContext.add(mdYs); } - mFontContext.add(font); - mNodes.add(node); - mContextLength++; pushIndices(); - top++; - - fontSize = getFontSize(); } private void pushIndices() { From 33371ffe244852cc1a4c2d3a948a12d81964bf2e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 06:43:11 +0300 Subject: [PATCH 082/198] Fix index naming --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 90530c61c..3c2d37fef 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -250,8 +250,8 @@ void popContext() { float nextX(float glyphWidth) { for (int index = mXsIndex; index >= 0; index--) { - int Index = mXIndices.get(index); - mXIndices.set(index, Index + 1); + int xIndex = mXIndices.get(index); + mXIndices.set(index, xIndex + 1); } int nextIndex = mXIndex + 1; @@ -269,8 +269,8 @@ float nextX(float glyphWidth) { float nextY() { for (int index = mYsIndex; index >= 0; index--) { - int Index = mYIndices.get(index); - mYIndices.set(index, Index + 1); + int yIndex = mYIndices.get(index); + mYIndices.set(index, yIndex + 1); } int nextIndex = mYIndex + 1; From 0620cb22953d5f11ecfcd35001160cf70d5763f6 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 15:42:22 +0300 Subject: [PATCH 083/198] Refactor: extract inline strings into private static final String constants. --- .../java/com/horcrux/svg/GlyphContext.java | 71 ++++++++++--------- .../java/com/horcrux/svg/TSpanShadowNode.java | 39 +++++++--- 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 3c2d37fef..361a6d5a7 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -20,8 +20,14 @@ class GlyphContext { static final float DEFAULT_FONT_SIZE = 12f; - private static final float DEFAULT_KERNING = 0f; - private static final float DEFAULT_LETTER_SPACING = 0f; + + private static final String KERNING = "kerning"; + private static final String FONT_SIZE = "fontSize"; + private static final String FONT_STYLE = "fontStyle"; + private static final String FONT_WEIGHT = "fontWeight"; + private static final String FONT_FAMILY = "fontFamily"; + private static final String LETTER_SPACING = "letterSpacing"; + private static final String IS_KERNING_VALUE_SET = "isKerningValueSet"; // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); @@ -51,10 +57,12 @@ class GlyphContext { // Cached per push context private double fontSize = DEFAULT_FONT_SIZE; + // Current values + private float mr; + // Current accumulated values private float mx; private float my; - private float mr; private float mdx; private float mdy; @@ -343,8 +351,8 @@ float getHeight() { for (int index = top; index >= 0; index--) { ReadableMap font = mFontContext.get(index); - if (mFontContext.get(index).hasKey("fontSize")) { - return font.getDouble("fontSize"); + if (mFontContext.get(index).hasKey(FONT_SIZE)) { + return font.getDouble(FONT_SIZE); } } @@ -357,11 +365,8 @@ float getHeight() { ReadableMap getFont() { WritableMap map = Arguments.createMap(); - - map.putDouble("letterSpacing", DEFAULT_LETTER_SPACING); - map.putBoolean("isKerningValueSet", false); - map.putDouble("kerning", DEFAULT_KERNING); - map.putDouble("fontSize", fontSize); + map.putBoolean(IS_KERNING_VALUE_SET, false); + map.putDouble(FONT_SIZE, fontSize); boolean letterSpacingSet = false; boolean fontFamilySet = false; @@ -372,38 +377,40 @@ ReadableMap getFont() { for (int index = top; index >= 0; index--) { ReadableMap font = mFontContext.get(index); - if (!fontFamilySet && font.hasKey("fontFamily")) { - String fontFamily = font.getString("fontFamily"); - map.putString("fontFamily", fontFamily); - fontFamilySet = true; - } - - if (!kerningSet && font.hasKey("kerning")) { - float kerning = Float.valueOf(font.getString("kerning")); - map.putBoolean("isKerningValueSet", true); - map.putDouble("kerning", kerning); - kerningSet = true; + if (!letterSpacingSet && font.hasKey(LETTER_SPACING)) { + String letterSpacingString = font.getString(LETTER_SPACING); + float letterSpacing = Float.valueOf(letterSpacingString); + map.putDouble(LETTER_SPACING, letterSpacing); + letterSpacingSet = true; } - if (!letterSpacingSet && font.hasKey("letterSpacing")) { - float letterSpacing = Float.valueOf(font.getString("letterSpacing")); - map.putDouble("letterSpacing", letterSpacing); - letterSpacingSet = true; + if (!fontFamilySet && font.hasKey(FONT_FAMILY)) { + String fontFamily = font.getString(FONT_FAMILY); + map.putString(FONT_FAMILY, fontFamily); + fontFamilySet = true; } - if (!fontWeightSet && font.hasKey("fontWeight")) { - String fontWeight = font.getString("fontWeight"); - map.putString("fontWeight", fontWeight); + if (!fontWeightSet && font.hasKey(FONT_WEIGHT)) { + String fontWeight = font.getString(FONT_WEIGHT); + map.putString(FONT_WEIGHT, fontWeight); fontWeightSet = true; } - if (!fontStyleSet && font.hasKey("fontStyle")) { - String fontStyle = font.getString("fontStyle"); - map.putString("fontStyle", fontStyle); + if (!fontStyleSet && font.hasKey(FONT_STYLE)) { + String fontStyle = font.getString(FONT_STYLE); + map.putString(FONT_STYLE, fontStyle); fontStyleSet = true; } - if (fontFamilySet && kerningSet && letterSpacingSet && fontWeightSet && fontStyleSet) { + if (!kerningSet && font.hasKey(KERNING)) { + String kerningString = font.getString(KERNING); + float kerning = Float.valueOf(kerningString); + map.putBoolean(IS_KERNING_VALUE_SET, true); + map.putDouble(KERNING, kerning); + kerningSet = true; + } + + if (letterSpacingSet && fontFamilySet && fontWeightSet && fontStyleSet && kerningSet) { break; } } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index f52d83750..3577bc377 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -33,9 +33,15 @@ */ class TSpanShadowNode extends TextShadowNode { - private Path mCache; - private @Nullable String mContent; - private TextPathShadowNode textPath; + private static final String STRETCH = "stretch"; + private static final String ITALIC = "italic"; + private static final String FONTS = "fonts/"; + private static final String BOLD = "bold"; + private static final String OTF = ".otf"; + private static final String TTF = ".ttf"; + + private static final float DEFAULT_KERNING = 0f; + private static final float DEFAULT_LETTER_SPACING = 0f; private static final String PROP_KERNING = "kerning"; private static final String PROP_FONT_SIZE = "fontSize"; @@ -45,6 +51,10 @@ class TSpanShadowNode extends TextShadowNode { private static final String PROP_LETTER_SPACING = "letterSpacing"; private static final String PROP_IS_KERNING_VALUE_SET = "isKerningValueSet"; + private Path mCache; + private @Nullable String mContent; + private TextPathShadowNode textPath; + @ReactProp(name = "content") public void setContent(@Nullable String content) { mContent = content; @@ -128,7 +138,7 @@ private Path getLinePath(String line, Paint paint) { offset = PropHelper.fromRelativeToFloat(startOffset, distance, 0, mScale, size); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" String method = textPath.getMethod(); // method = "align | stretch" - if ("stretch".equals(method)) { + if (STRETCH.equals(method)) { renderMethodScaling = distance / textMeasure; } } @@ -148,8 +158,13 @@ private Path getLinePath(String line, Paint paint) { String previous = ""; float previousWidth = 0; char[] chars = line.toCharArray(); - float kerning = (float) (font.getDouble(PROP_KERNING) * mScale); - boolean autoKerning = !font.getBoolean(PROP_IS_KERNING_VALUE_SET); + + boolean autoKerning = true; + float kerning = DEFAULT_KERNING; + if (font.getBoolean(PROP_IS_KERNING_VALUE_SET)) { + autoKerning = false; + kerning = (float) (font.getDouble(PROP_KERNING) * mScale); + } for (int index = 0; index < length; index++) { glyph = new Path(); @@ -205,7 +220,9 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { paint.setTextAlign(Paint.Align.LEFT); float fontSize = (float) font.getDouble(PROP_FONT_SIZE) * mScale; - float letterSpacing = (float) font.getDouble(PROP_LETTER_SPACING) * mScale; + float letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? + (float) font.getDouble(PROP_LETTER_SPACING) * mScale + : DEFAULT_LETTER_SPACING; paint.setTextSize(fontSize); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { @@ -218,9 +235,9 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { paint.setStrikeThruText(decoration == TEXT_DECORATION_LINE_THROUGH); boolean isBold = font.hasKey(PROP_FONT_WEIGHT) && - "bold".equals(font.getString(PROP_FONT_WEIGHT)); + BOLD.equals(font.getString(PROP_FONT_WEIGHT)); boolean isItalic = font.hasKey(PROP_FONT_STYLE) && - "italic".equals(font.getString(PROP_FONT_STYLE)); + ITALIC.equals(font.getString(PROP_FONT_STYLE)); int fontStyle; if (isBold && isItalic) { @@ -237,11 +254,11 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { Typeface tf = null; try { - String path = "fonts/" + font.getString(PROP_FONT_FAMILY) + ".otf"; + String path = FONTS + font.getString(PROP_FONT_FAMILY) + OTF; tf = Typeface.createFromAsset(a, path); } catch (Exception ignored) { try { - String path = "fonts/" + font.getString(PROP_FONT_FAMILY) + ".ttf"; + String path = FONTS + font.getString(PROP_FONT_FAMILY) + TTF; tf = Typeface.createFromAsset(a, path); } catch (Exception ignored2) { try { From d855b9605f75dfbe8f1266c71967bc046fc8576e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 16:00:19 +0300 Subject: [PATCH 084/198] Refactor: simplify applyTextPropertiesToPaint --- .../java/com/horcrux/svg/GlyphContext.java | 2 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 41 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 361a6d5a7..b2ae359af 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -103,9 +103,9 @@ class GlyphContext { mXsContext.add(mXs); mYsContext.add(mYs); + mRsContext.add(mRs); mdXsContext.add(mdXs); mdYsContext.add(mdYs); - mRsContext.add(mRs); mXIndices.add(mXIndex); mYIndices.add(mYIndex); diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 3577bc377..8be702ff3 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -162,8 +162,8 @@ private Path getLinePath(String line, Paint paint) { boolean autoKerning = true; float kerning = DEFAULT_KERNING; if (font.getBoolean(PROP_IS_KERNING_VALUE_SET)) { - autoKerning = false; kerning = (float) (font.getDouble(PROP_KERNING) * mScale); + autoKerning = false; } for (int index = 0; index < length; index++) { @@ -217,28 +217,26 @@ private Path getLinePath(String line, Paint paint) { } private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { - paint.setTextAlign(Paint.Align.LEFT); + AssetManager assetManager = getThemedContext().getResources().getAssets(); float fontSize = (float) font.getDouble(PROP_FONT_SIZE) * mScale; + float letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? (float) font.getDouble(PROP_LETTER_SPACING) * mScale : DEFAULT_LETTER_SPACING; - paint.setTextSize(fontSize); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - paint.setLetterSpacing(letterSpacing / fontSize); - } - - int decoration = getTextDecoration(); - - paint.setUnderlineText(decoration == TEXT_DECORATION_UNDERLINE); - paint.setStrikeThruText(decoration == TEXT_DECORATION_LINE_THROUGH); - 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 decoration = getTextDecoration(); + + boolean underlineText = decoration == TEXT_DECORATION_UNDERLINE; + + boolean strikeThruText = decoration == TEXT_DECORATION_LINE_THROUGH; + int fontStyle; if (isBold && isItalic) { fontStyle = Typeface.BOLD_ITALIC; @@ -250,26 +248,31 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { fontStyle = Typeface.NORMAL; } - AssetManager a = getThemedContext().getResources().getAssets(); - - Typeface tf = null; + Typeface typeface = null; try { String path = FONTS + font.getString(PROP_FONT_FAMILY) + OTF; - tf = Typeface.createFromAsset(a, path); + typeface = Typeface.createFromAsset(assetManager, path); } catch (Exception ignored) { try { String path = FONTS + font.getString(PROP_FONT_FAMILY) + TTF; - tf = Typeface.createFromAsset(a, path); + typeface = Typeface.createFromAsset(assetManager, path); } catch (Exception ignored2) { try { - tf = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle); + typeface = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle); } catch (Exception ignored3) { } } } // NB: if the font family is null / unsupported, the default one will be used - paint.setTypeface(tf); + paint.setTypeface(typeface); + paint.setTextSize(fontSize); + paint.setTextAlign(Paint.Align.LEFT); + paint.setUnderlineText(underlineText); + paint.setStrikeThruText(strikeThruText); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + paint.setLetterSpacing(letterSpacing / fontSize); + } } private void setupTextPath() { From 7c05e038b2cb8b4e121f0056c4a7e1eb5eda266b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 16:09:32 +0300 Subject: [PATCH 085/198] Refactor: simplify glyph translate calculation --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 8be702ff3..d413fc637 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -186,11 +186,12 @@ private Path getLinePath(String line, Paint paint) { dy = gc.nextDeltaY(); matrix = new Matrix(); - float start = offset + x + dx - width; + float xSum = offset + x + dx - width; + float ySum = y + dy; if (textPath != null) { float halfway = width / 2; - float midpoint = start + halfway; + float midpoint = xSum + halfway; if (midpoint > distance) { break; @@ -201,11 +202,11 @@ private Path getLinePath(String line, Paint paint) { assert pm != null; pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - matrix.preTranslate(-halfway, dy); - matrix.preScale(renderMethodScaling, 1); - matrix.postTranslate(0, y); + matrix.preTranslate(-halfway, 0); + matrix.preScale(renderMethodScaling, renderMethodScaling); + matrix.postTranslate(0, ySum); } else { - matrix.setTranslate(start, y + dy); + matrix.setTranslate(xSum, ySum); } matrix.preRotate(r); From 81044e480a59cde7ea67dd0b307e2fe14e8716c7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 16:20:04 +0300 Subject: [PATCH 086/198] Fix translate and rotate regressions. --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 8 +++----- .../src/main/java/com/horcrux/svg/TSpanShadowNode.java | 7 +++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index b2ae359af..35e2b9f40 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -298,11 +298,9 @@ float nextRotation() { mRIndices.set(index, rotationIndex + 1); } - int nextIndex = mRIndex + 1; - if (nextIndex < mRs.length) { - mRIndex = nextIndex; - mr = mRs[nextIndex]; - } + mRIndex = Math.min(mRIndex + 1, mRs.length - 1); + + mr = mRs[mRIndex]; return mr; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index d413fc637..d4e4aa44b 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -187,7 +187,6 @@ private Path getLinePath(String line, Paint paint) { matrix = new Matrix(); float xSum = offset + x + dx - width; - float ySum = y + dy; if (textPath != null) { float halfway = width / 2; @@ -202,11 +201,11 @@ private Path getLinePath(String line, Paint paint) { assert pm != null; pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - matrix.preTranslate(-halfway, 0); + matrix.preTranslate(-halfway, dy); matrix.preScale(renderMethodScaling, renderMethodScaling); - matrix.postTranslate(0, ySum); + matrix.postTranslate(0, y); } else { - matrix.setTranslate(xSum, ySum); + matrix.setTranslate(xSum, y + dy); } matrix.preRotate(r); From 1e2fd9b10253ae8a19281ddeb1f13c462028d9fd Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 16:27:13 +0300 Subject: [PATCH 087/198] Refactor, extract common index list incrementation logic into private static method. --- .../java/com/horcrux/svg/GlyphContext.java | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 35e2b9f40..a7e6e73da 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -256,11 +256,15 @@ void popContext() { } } - float nextX(float glyphWidth) { - for (int index = mXsIndex; index >= 0; index--) { - int xIndex = mXIndices.get(index); - mXIndices.set(index, xIndex + 1); + private static void incrementIndices(ArrayList indices, int topIndex) { + for (int index = topIndex; index >= 0; index--) { + int xIndex = indices.get(index); + indices.set(index, xIndex + 1); } + } + + float nextX(float glyphWidth) { + incrementIndices(mXIndices, mXsIndex); int nextIndex = mXIndex + 1; if (nextIndex < mXs.length) { @@ -276,10 +280,7 @@ float nextX(float glyphWidth) { } float nextY() { - for (int index = mYsIndex; index >= 0; index--) { - int yIndex = mYIndices.get(index); - mYIndices.set(index, yIndex + 1); - } + incrementIndices(mYIndices, mYsIndex); int nextIndex = mYIndex + 1; if (nextIndex < mYs.length) { @@ -293,10 +294,7 @@ float nextY() { } float nextRotation() { - for (int index = mRsIndex; index >= 0; index--) { - int rotationIndex = mRIndices.get(index); - mRIndices.set(index, rotationIndex + 1); - } + incrementIndices(mRIndices, mRsIndex); mRIndex = Math.min(mRIndex + 1, mRs.length - 1); @@ -306,10 +304,7 @@ float nextRotation() { } float nextDeltaX() { - for (int index = mdXsIndex; index >= 0; index--) { - int deltaIndex = mdXIndices.get(index); - mdXIndices.set(index, deltaIndex + 1); - } + incrementIndices(mdXIndices, mdXsIndex); int nextIndex = mdXIndex + 1; if (nextIndex < mdXs.length) { @@ -322,10 +317,7 @@ float nextDeltaX() { } float nextDeltaY() { - for (int index = mdYsIndex; index >= 0; index--) { - int deltaIndex = mdYIndices.get(index); - mdYIndices.set(index, deltaIndex + 1); - } + incrementIndices(mdYIndices, mdYsIndex); int nextIndex = mdYIndex + 1; if (nextIndex < mdYs.length) { From f4779efe9b9efeafe26d6c73c7d924ff426a21fc Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 17:13:34 +0300 Subject: [PATCH 088/198] Optimize input matrix handling. Improve get(Parent)TextRoot naming. Fix doc / comment. --- .../java/com/horcrux/svg/ImageShadowNode.java | 18 ++++------ .../horcrux/svg/LinearGradientShadowNode.java | 18 ++++------ .../main/java/com/horcrux/svg/PropHelper.java | 34 ++++++++++++------- .../horcrux/svg/RadialGradientShadowNode.java | 18 ++++------ .../java/com/horcrux/svg/VirtualNode.java | 30 +++++++--------- 5 files changed, 51 insertions(+), 67 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index e5ff63f85..b72d54e0f 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -55,8 +55,11 @@ class ImageShadowNode extends RenderableShadowNode { private int mMeetOrSlice; private final AtomicBoolean mLoading = new AtomicBoolean(false); - private static final float[] sMatrixData = new float[9]; - private static final float[] sRawMatrix = new float[9]; + private static final float[] sRawMatrix = new float[]{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; private Matrix mMatrix = new Matrix(); @ReactProp(name = "x") @@ -118,17 +121,8 @@ public void setMeetOrSlice(int meetOrSlice) { @ReactProp(name = "matrix") public void setMatrix(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale); if (matrixSize == 6) { - sRawMatrix[0] = sMatrixData[0]; - sRawMatrix[1] = sMatrixData[2]; - sRawMatrix[2] = sMatrixData[4] * mScale; - sRawMatrix[3] = sMatrixData[1]; - sRawMatrix[4] = sMatrixData[3]; - sRawMatrix[5] = sMatrixData[5] * mScale; - sRawMatrix[6] = 0; - sRawMatrix[7] = 0; - sRawMatrix[8] = 1; mMatrix.setValues(sRawMatrix); } else if (matrixSize != -1) { FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); diff --git a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java index 0513353a9..6c6026a56 100644 --- a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java @@ -32,8 +32,11 @@ class LinearGradientShadowNode extends DefinitionShadowNode { private ReadableArray mGradient; private Brush.BrushUnits mGradientUnits; - private static final float[] sMatrixData = new float[9]; - private static final float[] sRawMatrix = new float[9]; + private static final float[] sRawMatrix = new float[]{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; private Matrix mMatrix = new Matrix(); @ReactProp(name = "x1") @@ -82,17 +85,8 @@ public void setGradientUnits(int gradientUnits) { @ReactProp(name = "gradientTransform") public void setGradientTransform(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale); if (matrixSize == 6) { - sRawMatrix[0] = sMatrixData[0]; - sRawMatrix[1] = sMatrixData[2]; - sRawMatrix[2] = sMatrixData[4] * mScale; - sRawMatrix[3] = sMatrixData[1]; - sRawMatrix[4] = sMatrixData[3]; - sRawMatrix[5] = sMatrixData[5] * mScale; - sRawMatrix[6] = 0; - sRawMatrix[7] = 0; - sRawMatrix[8] = 1; mMatrix.setValues(sRawMatrix); } else if (matrixSize != -1) { FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 759e854a8..0a59519b9 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -48,27 +48,35 @@ float[] toFloatArray(@Nullable ReadableArray value) { return null; } - private static final int transformInputMatrixSize = 6; + private static final int inputMatrixDataSize = 6; /** - * Converts given {@link ReadableArray} to an array of {@code float}. Writes result to the array - * passed in {@param into}. This method will write to the output array up to the number of items - * from the input array. If the input array is longer than output the remaining part of the input - * will not be converted. + * 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 into output array - * @return number of items copied from input to the output array + * @param sRawMatrix output matrix + * @param mScale current resolution scaling + * @return size of input array */ - static int toMatrixData(ReadableArray value, float[] into) { + static int toMatrixData(ReadableArray value, float[] sRawMatrix, float mScale) { int fromSize = value.size(); - if (fromSize != transformInputMatrixSize) { + if (fromSize != inputMatrixDataSize) { return fromSize; } - for (int i = 0; i < transformInputMatrixSize; i++) { - into[i] = (float) value.getDouble(i); - } - return transformInputMatrixSize; + + 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; } static private final Pattern percentageRegExp = Pattern.compile("^(-?\\d+(?:\\.\\d+)?)%$"); diff --git a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java index 96d789a46..b40ed16e0 100644 --- a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java @@ -33,8 +33,11 @@ class RadialGradientShadowNode extends DefinitionShadowNode { private ReadableArray mGradient; private Brush.BrushUnits mGradientUnits; - private static final float[] sMatrixData = new float[9]; - private static final float[] sRawMatrix = new float[9]; + private static final float[] sRawMatrix = new float[]{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; private Matrix mMatrix = new Matrix(); @ReactProp(name = "fx") @@ -95,17 +98,8 @@ public void setGradientUnits(int gradientUnits) { @ReactProp(name = "gradientTransform") public void setGradientTransform(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale); if (matrixSize == 6) { - sRawMatrix[0] = sMatrixData[0]; - sRawMatrix[1] = sMatrixData[2]; - sRawMatrix[2] = sMatrixData[4] * mScale; - sRawMatrix[3] = sMatrixData[1]; - sRawMatrix[4] = sMatrixData[3]; - sRawMatrix[5] = sMatrixData[5] * mScale; - sRawMatrix[6] = 0; - sRawMatrix[7] = 0; - sRawMatrix[8] = 1; mMatrix.setValues(sRawMatrix); } else if (matrixSize != -1) { FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index c742aefd8..a259d7c9c 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -38,8 +38,11 @@ abstract class VirtualNode extends LayoutShadowNode { static final float MIN_OPACITY_FOR_DRAW = 0.01f; - private static final float[] sMatrixData = new float[9]; - private static final float[] sRawMatrix = new float[9]; + private static final float[] sRawMatrix = new float[]{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; float mOpacity = 1f; private double mFontSize = -1; private double mParentFontSize = -1; @@ -72,23 +75,23 @@ public boolean isVirtual() { } GroupShadowNode getTextRoot() { - GroupShadowNode shadowNode = getShadowNode(GroupShadowNode.class); + GroupShadowNode shadowNode = getTextRoot(GroupShadowNode.class); if (shadowNode == null) { - return getShadowNode(TextShadowNode.class); + return getTextRoot(TextShadowNode.class); } return shadowNode; } private GroupShadowNode getParentTextRoot() { - GroupShadowNode shadowNode = getParentShadowNode(GroupShadowNode.class); + GroupShadowNode shadowNode = getParentTextRoot(GroupShadowNode.class); if (shadowNode == null) { - return getParentShadowNode(TextShadowNode.class); + return getParentTextRoot(TextShadowNode.class); } return shadowNode; } @android.support.annotation.Nullable - private GroupShadowNode getParentShadowNode(Class shadowNodeClass) { + private GroupShadowNode getParentTextRoot(Class shadowNodeClass) { ReactShadowNode node = this.getParent(); if (mParentTextRoot == null) { while (node != null) { @@ -111,7 +114,7 @@ private GroupShadowNode getParentShadowNode(Class shadowNodeClass) { } @android.support.annotation.Nullable - private GroupShadowNode getShadowNode(Class shadowNodeClass) { + private GroupShadowNode getTextRoot(Class shadowNodeClass) { VirtualNode node = this; if (mTextRoot == null) { while (node != null) { @@ -214,17 +217,8 @@ public void setOpacity(float opacity) { @ReactProp(name = "matrix") public void setMatrix(@Nullable ReadableArray matrixArray) { if (matrixArray != null) { - int matrixSize = PropHelper.toMatrixData(matrixArray, sMatrixData); + int matrixSize = PropHelper.toMatrixData(matrixArray, sRawMatrix, mScale); if (matrixSize == 6) { - sRawMatrix[0] = sMatrixData[0]; - sRawMatrix[1] = sMatrixData[2]; - sRawMatrix[2] = sMatrixData[4] * mScale; - sRawMatrix[3] = sMatrixData[1]; - sRawMatrix[4] = sMatrixData[3]; - sRawMatrix[5] = sMatrixData[5] * mScale; - sRawMatrix[6] = 0; - sRawMatrix[7] = 0; - sRawMatrix[8] = 1; mMatrix.setValues(sRawMatrix); } else if (matrixSize != -1) { FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); From ea17841eb5d1806eb1476ecd15566a3a0928fccc Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 17:19:09 +0300 Subject: [PATCH 089/198] Simplify manual kerning logic. --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 3 --- android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index a7e6e73da..b952f81d5 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -27,7 +27,6 @@ class GlyphContext { private static final String FONT_WEIGHT = "fontWeight"; private static final String FONT_FAMILY = "fontFamily"; private static final String LETTER_SPACING = "letterSpacing"; - private static final String IS_KERNING_VALUE_SET = "isKerningValueSet"; // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); @@ -355,7 +354,6 @@ float getHeight() { ReadableMap getFont() { WritableMap map = Arguments.createMap(); - map.putBoolean(IS_KERNING_VALUE_SET, false); map.putDouble(FONT_SIZE, fontSize); boolean letterSpacingSet = false; @@ -395,7 +393,6 @@ ReadableMap getFont() { if (!kerningSet && font.hasKey(KERNING)) { String kerningString = font.getString(KERNING); float kerning = Float.valueOf(kerningString); - map.putBoolean(IS_KERNING_VALUE_SET, true); map.putDouble(KERNING, kerning); kerningSet = true; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index d4e4aa44b..db1cc2615 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -49,7 +49,6 @@ class TSpanShadowNode extends TextShadowNode { private static final String PROP_FONT_WEIGHT = "fontWeight"; private static final String PROP_FONT_FAMILY = "fontFamily"; private static final String PROP_LETTER_SPACING = "letterSpacing"; - private static final String PROP_IS_KERNING_VALUE_SET = "isKerningValueSet"; private Path mCache; private @Nullable String mContent; @@ -161,7 +160,7 @@ private Path getLinePath(String line, Paint paint) { boolean autoKerning = true; float kerning = DEFAULT_KERNING; - if (font.getBoolean(PROP_IS_KERNING_VALUE_SET)) { + if (font.hasKey(PROP_KERNING)) { kerning = (float) (font.getDouble(PROP_KERNING) * mScale); autoKerning = false; } From f9359a62362c9bdc8a570a365779bbfdfa26d72f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 17:23:08 +0300 Subject: [PATCH 090/198] Remove redundant mr variable. --- android/src/main/java/com/horcrux/svg/GlyphContext.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index b952f81d5..a47316fde 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -56,9 +56,6 @@ class GlyphContext { // Cached per push context private double fontSize = DEFAULT_FONT_SIZE; - // Current values - private float mr; - // Current accumulated values private float mx; private float my; @@ -200,7 +197,7 @@ private void pushIndices() { private void reset() { mXsIndex = mYsIndex = mRsIndex = mdXsIndex = mdYsIndex = 0; mXIndex = mYIndex = mRIndex = mdXIndex = mdYIndex = -1; - mx = my = mr = mdx = mdy = 0; + mx = my = mdx = mdy = 0; } void popContext() { @@ -297,9 +294,7 @@ float nextRotation() { mRIndex = Math.min(mRIndex + 1, mRs.length - 1); - mr = mRs[mRIndex]; - - return mr; + return mRs[mRIndex]; } float nextDeltaX() { From 39cdabe17eb02d2de4ec6a4305f9fb0eeb626d20 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 18:12:22 +0300 Subject: [PATCH 091/198] Fix null handling in setMatrix --- android/src/main/java/com/horcrux/svg/ImageShadowNode.java | 5 ++++- .../main/java/com/horcrux/svg/LinearGradientShadowNode.java | 5 ++++- .../main/java/com/horcrux/svg/RadialGradientShadowNode.java | 5 ++++- android/src/main/java/com/horcrux/svg/VirtualNode.java | 3 +++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index b72d54e0f..67504a07c 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -60,7 +60,7 @@ class ImageShadowNode extends RenderableShadowNode { 0, 1, 0, 0, 0, 1 }; - private Matrix mMatrix = new Matrix(); + private Matrix mMatrix = null; @ReactProp(name = "x") public void setX(String x) { @@ -123,6 +123,9 @@ public void setMatrix(@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"); diff --git a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java index 6c6026a56..1f19d8861 100644 --- a/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java @@ -37,7 +37,7 @@ class LinearGradientShadowNode extends DefinitionShadowNode { 0, 1, 0, 0, 0, 1 }; - private Matrix mMatrix = new Matrix(); + private Matrix mMatrix = null; @ReactProp(name = "x1") public void setX1(String x1) { @@ -87,6 +87,9 @@ 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"); diff --git a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java index b40ed16e0..6f3dd88c8 100644 --- a/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java @@ -38,7 +38,7 @@ class RadialGradientShadowNode extends DefinitionShadowNode { 0, 1, 0, 0, 0, 1 }; - private Matrix mMatrix = new Matrix(); + private Matrix mMatrix = null; @ReactProp(name = "fx") public void setFx(String fx) { @@ -100,6 +100,9 @@ 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"); diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index a259d7c9c..989ea3207 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -219,6 +219,9 @@ public void setMatrix(@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"); From 9cd955a82e882ec7cff282e2d03414c0632b182c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 18:54:32 +0300 Subject: [PATCH 092/198] Allow generic units in dx and dy, and percentages in rotate text attributes. --- .../main/java/com/horcrux/svg/GlyphContext.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index a47316fde..908742a07 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -163,7 +163,7 @@ void pushContext( mRsIndex++; mRIndex = -1; mRIndices.add(mRIndex); - mRs = getFloatArrayFromReadableArray(rotate); + mRs = getFloatArrayFromReadableArray(rotate, 360); mRsContext.add(mRs); } @@ -171,7 +171,7 @@ void pushContext( mdXsIndex++; mdXIndex = -1; mdXIndices.add(mdXIndex); - mdXs = getFloatArrayFromReadableArray(deltaX); + mdXs = getFloatArrayFromReadableArray(deltaX, mWidth / mScale); mdXsContext.add(mdXs); } @@ -179,7 +179,7 @@ void pushContext( mdYsIndex++; mdYIndex = -1; mdYIndices.add(mdYIndex); - mdYs = getFloatArrayFromReadableArray(deltaY); + mdYs = getFloatArrayFromReadableArray(deltaY, mHeight / mScale); mdYsContext.add(mdYs); } @@ -400,16 +400,14 @@ ReadableMap getFont() { return map; } - private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { + private float[] getFloatArrayFromReadableArray(ReadableArray readableArray, float dim) { int size = readableArray.size(); float[] floats = new float[size]; for (int i = 0; i < size; i++) { switch (readableArray.getType(i)) { case String: - // em units - String val = readableArray.getString(i); - String substring = val.substring(0, val.length() - 2); - floats[i] = (float) (Float.valueOf(substring) * fontSize); + String string = readableArray.getString(i); + floats[i] = PropHelper.fromRelativeToFloat(string, dim, 0, 1, fontSize); break; case Number: From 5372355b1116b31684434c24fc0567edf03767a5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 19:11:41 +0300 Subject: [PATCH 093/198] Fix parsing of generic units in dx and dy, and percentages in rotate text attributes. --- lib/extract/extractText.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 6ec3bb643..dd4c61c0a 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -77,14 +77,7 @@ export function extractFont(props) { function parseDelta(delta) { if (typeof delta === 'string') { - const trim = delta.trim(); - if (trim.slice(-2) === 'em') { - return [trim]; - } else if (isNaN(+delta)) { - return trim.replace(commaReg, ' ').split(spaceReg).map(d => +d || 0); - } else { - return [+delta]; - } + return delta.trim().replace(commaReg, ' ').split(spaceReg); } else if (typeof delta === 'number') { return [delta]; } else { From 6b022ac86199235fee092d575c46d5377211dc62 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 20:33:05 +0300 Subject: [PATCH 094/198] Harmonize parseSVGLengthList usage and naming. Probably requires api change in ios as well. --- .../java/com/horcrux/svg/GlyphContext.java | 144 +++++++++++------- .../java/com/horcrux/svg/TSpanShadowNode.java | 3 +- .../java/com/horcrux/svg/TextShadowNode.java | 12 +- lib/extract/extractText.js | 23 +-- 4 files changed, 106 insertions(+), 76 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 908742a07..75b6b7a90 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; +// https://www.w3.org/TR/SVG/text.html#TSpanElement class GlyphContext { static final float DEFAULT_FONT_SIZE = 12f; @@ -31,23 +32,23 @@ class GlyphContext { // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); private final ArrayList mYsContext = new ArrayList<>(); + private final ArrayList mdXsContext = new ArrayList<>(); + private final ArrayList mdYsContext = new ArrayList<>(); private final ArrayList mRsContext = new ArrayList<>(); - private final ArrayList mdXsContext = new ArrayList<>(); - private final ArrayList mdYsContext = new ArrayList<>(); // Unique index into attribute list (one per unique list) private final ArrayList mXIndices = new ArrayList<>(); private final ArrayList mYIndices = new ArrayList<>(); - private final ArrayList mRIndices = new ArrayList<>(); private final ArrayList mdXIndices = new ArrayList<>(); private final ArrayList mdYIndices = new ArrayList<>(); + private final ArrayList mRIndices = new ArrayList<>(); // Index of unique context used (one per node push/pop) private final ArrayList mXsIndices = new ArrayList<>(); private final ArrayList mYsIndices = new ArrayList<>(); - private final ArrayList mRsIndices = new ArrayList<>(); private final ArrayList mdXsIndices = new ArrayList<>(); private final ArrayList mdYsIndices = new ArrayList<>(); + private final ArrayList mRsIndices = new ArrayList<>(); // Current stack (one per node push/pop) private final ArrayList mFontContext = new ArrayList<>(); @@ -57,31 +58,53 @@ class GlyphContext { private double fontSize = DEFAULT_FONT_SIZE; // Current accumulated values + // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate + // syntax is the same as that for private float mx; private float my; + + // https://www.w3.org/TR/SVG/types.html#Length private float mdx; private float mdy; - // Current attribute list + // Current SVGLengthList + // https://www.w3.org/TR/SVG/types.html#InterfaceSVGLengthList + // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinates + + // https://www.w3.org/TR/SVG/text.html#TSpanElementXAttribute private String[] mXs = new String[]{}; + + // https://www.w3.org/TR/SVG/text.html#TSpanElementYAttribute private String[] mYs = new String[]{}; + + // Current SVGLengthList + // https://www.w3.org/TR/SVG/types.html#DataTypeLengths + + // https://www.w3.org/TR/SVG/text.html#TSpanElementDXAttribute + private String[] mdXs = new String[]{}; + + // https://www.w3.org/TR/SVG/text.html#TSpanElementDYAttribute + private String[] mdYs = new String[]{}; + + // Current SVGLengthList + // https://www.w3.org/TR/SVG/types.html#DataTypeNumbers + + // https://www.w3.org/TR/SVG/text.html#TSpanElementRotateAttribute private float[] mRs = new float[]{0}; - private float[] mdXs = new float[]{}; - private float[] mdYs = new float[]{}; // Current attribute list index private int mXsIndex; private int mYsIndex; - private int mRsIndex; private int mdXsIndex; private int mdYsIndex; + private int mRsIndex; // Current value index in current attribute list private int mXIndex = -1; private int mYIndex = -1; - private int mRIndex = -1; private int mdXIndex = -1; private int mdYIndex = -1; + private int mRIndex = -1; // Stack length and last index private int mContextLength; @@ -99,15 +122,15 @@ class GlyphContext { mXsContext.add(mXs); mYsContext.add(mYs); - mRsContext.add(mRs); mdXsContext.add(mdXs); mdYsContext.add(mdYs); + mRsContext.add(mRs); mXIndices.add(mXIndex); mYIndices.add(mYIndex); - mRIndices.add(mRIndex); mdXIndices.add(mdXIndex); mdYIndices.add(mdYIndex); + mRIndices.add(mRIndex); pushIndices(); } @@ -126,8 +149,8 @@ void pushContext( boolean reset, TextShadowNode node, @Nullable ReadableMap font, - @Nullable String x, - @Nullable String y, + @Nullable ReadableArray x, + @Nullable ReadableArray y, @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, @Nullable ReadableArray deltaY @@ -143,35 +166,27 @@ void pushContext( fontSize = getFontSize(); - if (x != null) { + if (x != null && x.size() != 0) { mXsIndex++; mXIndex = -1; mXIndices.add(mXIndex); - mXs = x.trim().split("\\s+"); + mXs = getStringArrayFromReadableArray(x); mXsContext.add(mXs); } - if (y != null) { + if (y != null && y.size() != 0) { mYsIndex++; mYIndex = -1; mYIndices.add(mYIndex); - mYs = y.trim().split("\\s+"); + mYs = getStringArrayFromReadableArray(y); mYsContext.add(mYs); } - if (rotate != null && rotate.size() != 0) { - mRsIndex++; - mRIndex = -1; - mRIndices.add(mRIndex); - mRs = getFloatArrayFromReadableArray(rotate, 360); - mRsContext.add(mRs); - } - if (deltaX != null && deltaX.size() != 0) { mdXsIndex++; mdXIndex = -1; mdXIndices.add(mdXIndex); - mdXs = getFloatArrayFromReadableArray(deltaX, mWidth / mScale); + mdXs = getStringArrayFromReadableArray(deltaX); mdXsContext.add(mdXs); } @@ -179,33 +194,41 @@ void pushContext( mdYsIndex++; mdYIndex = -1; mdYIndices.add(mdYIndex); - mdYs = getFloatArrayFromReadableArray(deltaY, mHeight / mScale); + mdYs = getStringArrayFromReadableArray(deltaY); mdYsContext.add(mdYs); } + if (rotate != null && rotate.size() != 0) { + mRsIndex++; + mRIndex = -1; + mRIndices.add(mRIndex); + mRs = getFloatArrayFromReadableArray(rotate); + mRsContext.add(mRs); + } + pushIndices(); } private void pushIndices() { mXsIndices.add(mXsIndex); mYsIndices.add(mYsIndex); - mRsIndices.add(mRsIndex); mdXsIndices.add(mdXsIndex); mdYsIndices.add(mdYsIndex); + mRsIndices.add(mRsIndex); } private void reset() { - mXsIndex = mYsIndex = mRsIndex = mdXsIndex = mdYsIndex = 0; - mXIndex = mYIndex = mRIndex = mdXIndex = mdYIndex = -1; + mXsIndex = mYsIndex = mdXsIndex = mdYsIndex = mRsIndex = 0; + mXIndex = mYIndex = mdXIndex = mdYIndex = mRIndex = -1; mx = my = mdx = mdy = 0; } void popContext() { mXsIndices.remove(mContextLength); mYsIndices.remove(mContextLength); - mRsIndices.remove(mContextLength); mdXsIndices.remove(mContextLength); mdYsIndices.remove(mContextLength); + mRsIndices.remove(mContextLength); mContextLength--; top--; @@ -215,15 +238,15 @@ void popContext() { int x = mXsIndex; int y = mYsIndex; - int r = mRsIndex; int dx = mdXsIndex; int dy = mdYsIndex; + int r = mRsIndex; mXsIndex = mXsIndices.get(mContextLength); mYsIndex = mYsIndices.get(mContextLength); - mRsIndex = mRsIndices.get(mContextLength); mdXsIndex = mdXsIndices.get(mContextLength); mdYsIndex = mdYsIndices.get(mContextLength); + mRsIndex = mRsIndices.get(mContextLength); if (x != mXsIndex) { mXsContext.remove(x); @@ -235,11 +258,6 @@ void popContext() { mYs = mYsContext.get(mYsIndex); mYIndex = mYIndices.get(mYsIndex); } - if (r != mRsIndex) { - mRsContext.remove(r); - mRs = mRsContext.get(mRsIndex); - mRIndex = mRIndices.get(mRsIndex); - } if (dx != mdXsIndex) { mdXsContext.remove(dx); mdXs = mdXsContext.get(mdXsIndex); @@ -250,6 +268,11 @@ void popContext() { mdYs = mdYsContext.get(mdYsIndex); mdYIndex = mdYIndices.get(mdYsIndex); } + if (r != mRsIndex) { + mRsContext.remove(r); + mRs = mRsContext.get(mRsIndex); + mRIndex = mRIndices.get(mRsIndex); + } } private static void incrementIndices(ArrayList indices, int topIndex) { @@ -289,21 +312,14 @@ float nextY() { return my; } - float nextRotation() { - incrementIndices(mRIndices, mRsIndex); - - mRIndex = Math.min(mRIndex + 1, mRs.length - 1); - - return mRs[mRIndex]; - } - float nextDeltaX() { incrementIndices(mdXIndices, mdXsIndex); int nextIndex = mdXIndex + 1; if (nextIndex < mdXs.length) { mdXIndex = nextIndex; - float val = mdXs[nextIndex]; + String string = mdXs[nextIndex]; + float val = PropHelper.fromRelativeToFloat(string, mWidth, 0, 1, fontSize); mdx += val * mScale; } @@ -316,13 +332,22 @@ float nextDeltaY() { int nextIndex = mdYIndex + 1; if (nextIndex < mdYs.length) { mdYIndex = nextIndex; - float val = mdYs[nextIndex]; + String string = mdYs[nextIndex]; + float val = PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, fontSize); mdy += val * mScale; } return mdy; } + float nextRotation() { + incrementIndices(mRIndices, mRsIndex); + + mRIndex = Math.min(mRIndex + 1, mRs.length - 1); + + return mRs[mRIndex]; + } + float getWidth() { return mWidth; } @@ -400,20 +425,21 @@ ReadableMap getFont() { return map; } - private float[] getFloatArrayFromReadableArray(ReadableArray readableArray, float dim) { + private String[] getStringArrayFromReadableArray(ReadableArray readableArray) { + int size = readableArray.size(); + String[] strings = new String[size]; + for (int i = 0; i < size; i++) { + strings[i] = readableArray.getString(i); + } + return strings; + } + + private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { int size = readableArray.size(); float[] floats = new float[size]; for (int i = 0; i < size; i++) { - switch (readableArray.getType(i)) { - case String: - String string = readableArray.getString(i); - floats[i] = PropHelper.fromRelativeToFloat(string, dim, 0, 1, fontSize); - break; - - case Number: - floats[i] = (float) readableArray.getDouble(i); - break; - } + String string = readableArray.getString(i); + floats[i] = Float.valueOf(string); } return floats; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index db1cc2615..f141f9c0d 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -180,9 +180,10 @@ private Path getLinePath(String line, Paint paint) { x = gc.nextX(width + kerning); y = gc.nextY(); - r = gc.nextRotation(); dx = gc.nextDeltaX(); dy = gc.nextDeltaY(); + r = gc.nextRotation(); + matrix = new Matrix(); float xSum = offset + x + dx - width; diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 74b60b96c..b85930abd 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -39,11 +39,11 @@ class TextShadowNode extends GroupShadowNode { private int mTextAnchor = TEXT_ANCHOR_AUTO; private int mTextDecoration = TEXT_DECORATION_NONE; - private @Nullable ReadableArray mRotate; - private @Nullable ReadableArray mDeltaX; + private @Nullable ReadableArray mPositionX; + private @Nullable ReadableArray mPositionY; + private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaY; - private @Nullable String mPositionX; - private @Nullable String mPositionY; + private @Nullable ReadableArray mRotate; @ReactProp(name = "textAnchor") public void setTextAnchor(int textAnchor) { @@ -76,13 +76,13 @@ public void setDeltaY(@Nullable ReadableArray deltaY) { } @ReactProp(name = "positionX") - public void setPositionX(@Nullable String positionX) { + public void setPositionX(@Nullable ReadableArray positionX) { mPositionX = positionX; markUpdated(); } @ReactProp(name = "positionY") - public void setPositionY(@Nullable String positionY) { + public void setPositionY(@Nullable ReadableArray positionY) { mPositionY = positionY; markUpdated(); } diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index dd4c61c0a..3eba521a6 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -75,18 +75,20 @@ export function extractFont(props) { return _.defaults(ownedFont, font); } -function parseDelta(delta) { +function parseSVGLengthList(delta) { if (typeof delta === 'string') { return delta.trim().replace(commaReg, ' ').split(spaceReg); } else if (typeof delta === 'number') { - return [delta]; + return [delta.toString()]; + } else if (delta && typeof delta.map === 'function') { + return delta.map(d => `${d}`); } else { return []; } } export default function(props, container) { - const { + let { x, y, dx, @@ -99,14 +101,15 @@ export default function(props, container) { textDecoration } = props; + let positionX = parseSVGLengthList(x); + let positionY = parseSVGLengthList(y); + const deltaX = parseSVGLengthList(dx); + const deltaY = parseSVGLengthList(dy); + rotate = parseSVGLengthList(rotate); - const deltaX = parseDelta(dx); - const deltaY = parseDelta(dy); - const rotates = parseDelta(rotate); let { children } = props; let content = null; - if (typeof children === 'string' || typeof children === 'number') { const childrenString = children.toString(); if (container) { @@ -131,13 +134,13 @@ export default function(props, container) { font: extractFont(props), children, content, + positionX, + positionY, + rotate, deltaX, deltaY, - rotate: rotates, method, spacing, startOffset: (startOffset || 0).toString(), - positionX: _.isNil(x) ? null : x.toString(), - positionY: _.isNil(y) ? null : y.toString() }; } From ed9c0bd647c78d3b1c0be7498e5de2f9a186006f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 21:59:13 +0300 Subject: [PATCH 095/198] Restructure fontSize handling. --- .../java/com/horcrux/svg/GlyphContext.java | 294 +++++++++--------- .../java/com/horcrux/svg/TextShadowNode.java | 2 +- .../java/com/horcrux/svg/VirtualNode.java | 8 +- lib/extract/extractText.js | 13 +- 4 files changed, 161 insertions(+), 156 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 75b6b7a90..13f23ccaf 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -32,40 +32,40 @@ class GlyphContext { // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); private final ArrayList mYsContext = new ArrayList<>(); - private final ArrayList mdXsContext = new ArrayList<>(); - private final ArrayList mdYsContext = new ArrayList<>(); + private final ArrayList mDXsContext = new ArrayList<>(); + private final ArrayList mDYsContext = new ArrayList<>(); private final ArrayList mRsContext = new ArrayList<>(); // Unique index into attribute list (one per unique list) private final ArrayList mXIndices = new ArrayList<>(); private final ArrayList mYIndices = new ArrayList<>(); - private final ArrayList mdXIndices = new ArrayList<>(); - private final ArrayList mdYIndices = new ArrayList<>(); + private final ArrayList mDXIndices = new ArrayList<>(); + private final ArrayList mDYIndices = new ArrayList<>(); private final ArrayList mRIndices = new ArrayList<>(); // Index of unique context used (one per node push/pop) private final ArrayList mXsIndices = new ArrayList<>(); private final ArrayList mYsIndices = new ArrayList<>(); - private final ArrayList mdXsIndices = new ArrayList<>(); - private final ArrayList mdYsIndices = new ArrayList<>(); + private final ArrayList mDXsIndices = new ArrayList<>(); + private final ArrayList mDYsIndices = new ArrayList<>(); private final ArrayList mRsIndices = new ArrayList<>(); // Current stack (one per node push/pop) private final ArrayList mFontContext = new ArrayList<>(); private final ArrayList mNodes = new ArrayList<>(); - // Cached per push context - private double fontSize = DEFAULT_FONT_SIZE; + // Cleared on push context, cached on getFontSize + private float mFontSize = DEFAULT_FONT_SIZE; // Current accumulated values // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate // syntax is the same as that for - private float mx; - private float my; + private float mX; + private float mY; // https://www.w3.org/TR/SVG/types.html#Length - private float mdx; - private float mdy; + private float mDX; + private float mDY; // Current SVGLengthList // https://www.w3.org/TR/SVG/types.html#InterfaceSVGLengthList @@ -81,10 +81,10 @@ class GlyphContext { // https://www.w3.org/TR/SVG/types.html#DataTypeLengths // https://www.w3.org/TR/SVG/text.html#TSpanElementDXAttribute - private String[] mdXs = new String[]{}; + private String[] mDXs = new String[]{}; // https://www.w3.org/TR/SVG/text.html#TSpanElementDYAttribute - private String[] mdYs = new String[]{}; + private String[] mDYs = new String[]{}; // Current SVGLengthList // https://www.w3.org/TR/SVG/types.html#DataTypeNumbers @@ -95,26 +95,34 @@ class GlyphContext { // Current attribute list index private int mXsIndex; private int mYsIndex; - private int mdXsIndex; - private int mdYsIndex; + private int mDXsIndex; + private int mDYsIndex; private int mRsIndex; // Current value index in current attribute list private int mXIndex = -1; private int mYIndex = -1; - private int mdXIndex = -1; - private int mdYIndex = -1; + private int mDXIndex = -1; + private int mDYIndex = -1; private int mRIndex = -1; // Stack length and last index - private int mContextLength; - private int top = -1; + private int mIndexTop; + private int mTop = -1; // Constructor parameters private final float mScale; private final float mWidth; private final float mHeight; + private void pushIndices() { + mXsIndices.add(mXsIndex); + mYsIndices.add(mYsIndex); + mDXsIndices.add(mDXsIndex); + mDYsIndices.add(mDYsIndex); + mRsIndices.add(mRsIndex); + } + GlyphContext(float scale, float width, float height) { mScale = scale; mWidth = width; @@ -122,27 +130,55 @@ class GlyphContext { mXsContext.add(mXs); mYsContext.add(mYs); - mdXsContext.add(mdXs); - mdYsContext.add(mdYs); + mDXsContext.add(mDXs); + mDYsContext.add(mDYs); mRsContext.add(mRs); mXIndices.add(mXIndex); mYIndices.add(mYIndex); - mdXIndices.add(mdXIndex); - mdYIndices.add(mdYIndex); + mDXIndices.add(mDXIndex); + mDYIndices.add(mDYIndex); mRIndices.add(mRIndex); pushIndices(); } - void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { + private void reset() { + mXsIndex = mYsIndex = mDXsIndex = mDYsIndex = mRsIndex = 0; + mXIndex = mYIndex = mDXIndex = mDYIndex = mRIndex = -1; + mX = mY = mDX = mDY = 0; + } + + private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { mFontContext.add(font); mNodes.add(node); - mContextLength++; + mFontSize = -1; + mIndexTop++; + mTop++; + } + + void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { + pushNodeAndFont(node, font); pushIndices(); - top++; + } - fontSize = getFontSize(); + private String[] getStringArrayFromReadableArray(ReadableArray readableArray) { + int size = readableArray.size(); + String[] strings = new String[size]; + for (int i = 0; i < size; i++) { + strings[i] = readableArray.getString(i); + } + return strings; + } + + private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { + int size = readableArray.size(); + float[] floats = new float[size]; + for (int i = 0; i < size; i++) { + String string = readableArray.getString(i); + floats[i] = Float.valueOf(string); + } + return floats; } void pushContext( @@ -159,12 +195,7 @@ void pushContext( this.reset(); } - mFontContext.add(font); - mNodes.add(node); - mContextLength++; - top++; - - fontSize = getFontSize(); + pushNodeAndFont(node, font); if (x != null && x.size() != 0) { mXsIndex++; @@ -183,19 +214,19 @@ void pushContext( } if (deltaX != null && deltaX.size() != 0) { - mdXsIndex++; - mdXIndex = -1; - mdXIndices.add(mdXIndex); - mdXs = getStringArrayFromReadableArray(deltaX); - mdXsContext.add(mdXs); + mDXsIndex++; + mDXIndex = -1; + mDXIndices.add(mDXIndex); + mDXs = getStringArrayFromReadableArray(deltaX); + mDXsContext.add(mDXs); } if (deltaY != null && deltaY.size() != 0) { - mdYsIndex++; - mdYIndex = -1; - mdYIndices.add(mdYIndex); - mdYs = getStringArrayFromReadableArray(deltaY); - mdYsContext.add(mdYs); + mDYsIndex++; + mDYIndex = -1; + mDYIndices.add(mDYIndex); + mDYs = getStringArrayFromReadableArray(deltaY); + mDYsContext.add(mDYs); } if (rotate != null && rotate.size() != 0) { @@ -209,44 +240,30 @@ void pushContext( pushIndices(); } - private void pushIndices() { - mXsIndices.add(mXsIndex); - mYsIndices.add(mYsIndex); - mdXsIndices.add(mdXsIndex); - mdYsIndices.add(mdYsIndex); - mRsIndices.add(mRsIndex); - } - - private void reset() { - mXsIndex = mYsIndex = mdXsIndex = mdYsIndex = mRsIndex = 0; - mXIndex = mYIndex = mdXIndex = mdYIndex = mRIndex = -1; - mx = my = mdx = mdy = 0; - } - void popContext() { - mXsIndices.remove(mContextLength); - mYsIndices.remove(mContextLength); - mdXsIndices.remove(mContextLength); - mdYsIndices.remove(mContextLength); - mRsIndices.remove(mContextLength); + mXsIndices.remove(mIndexTop); + mYsIndices.remove(mIndexTop); + mDXsIndices.remove(mIndexTop); + mDYsIndices.remove(mIndexTop); + mRsIndices.remove(mIndexTop); - mContextLength--; - top--; + mFontContext.remove(mTop); + mNodes.remove(mTop); - mNodes.remove(mContextLength); - mFontContext.remove(mContextLength); + mIndexTop--; + mTop--; int x = mXsIndex; int y = mYsIndex; - int dx = mdXsIndex; - int dy = mdYsIndex; + int dx = mDXsIndex; + int dy = mDYsIndex; int r = mRsIndex; - mXsIndex = mXsIndices.get(mContextLength); - mYsIndex = mYsIndices.get(mContextLength); - mdXsIndex = mdXsIndices.get(mContextLength); - mdYsIndex = mdYsIndices.get(mContextLength); - mRsIndex = mRsIndices.get(mContextLength); + mXsIndex = mXsIndices.get(mIndexTop); + mYsIndex = mYsIndices.get(mIndexTop); + mDXsIndex = mDXsIndices.get(mIndexTop); + mDYsIndex = mDYsIndices.get(mIndexTop); + mRsIndex = mRsIndices.get(mIndexTop); if (x != mXsIndex) { mXsContext.remove(x); @@ -258,15 +275,15 @@ void popContext() { mYs = mYsContext.get(mYsIndex); mYIndex = mYIndices.get(mYsIndex); } - if (dx != mdXsIndex) { - mdXsContext.remove(dx); - mdXs = mdXsContext.get(mdXsIndex); - mdXIndex = mdXIndices.get(mdXsIndex); + if (dx != mDXsIndex) { + mDXsContext.remove(dx); + mDXs = mDXsContext.get(mDXsIndex); + mDXIndex = mDXIndices.get(mDXsIndex); } - if (dy != mdYsIndex) { - mdYsContext.remove(dy); - mdYs = mdYsContext.get(mdYsIndex); - mdYIndex = mdYIndices.get(mdYsIndex); + if (dy != mDYsIndex) { + mDYsContext.remove(dy); + mDYs = mDYsContext.get(mDYsIndex); + mDYIndex = mDYIndices.get(mDYsIndex); } if (r != mRsIndex) { mRsContext.remove(r); @@ -282,20 +299,44 @@ private static void incrementIndices(ArrayList indices, int topIndex) { } } + private float getActualFontSize() { + for (int index = mTop; index >= 0; index--) { + ReadableMap font = mFontContext.get(index); + + if (mFontContext.get(index).hasKey(FONT_SIZE)) { + String string = font.getString(FONT_SIZE); + return PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, DEFAULT_FONT_SIZE); + } + } + + if (mTop > -1) { + return mNodes.get(0).getFontSizeFromParentContext(); + } + + return DEFAULT_FONT_SIZE; + } + + float getFontSize() { + if (mFontSize == -1) { + mFontSize = getActualFontSize(); + } + return mFontSize; + } + float nextX(float glyphWidth) { incrementIndices(mXIndices, mXsIndex); int nextIndex = mXIndex + 1; if (nextIndex < mXs.length) { - mdx = 0; + mDX = 0; mXIndex = nextIndex; String val = mXs[nextIndex]; - mx = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, fontSize); + mX = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, getFontSize()); } - mx += glyphWidth; + mX += glyphWidth; - return mx; + return mX; } float nextY() { @@ -303,41 +344,41 @@ float nextY() { int nextIndex = mYIndex + 1; if (nextIndex < mYs.length) { - mdy = 0; + mDY = 0; mYIndex = nextIndex; String val = mYs[nextIndex]; - my = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, fontSize); + mY = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, getFontSize()); } - return my; + return mY; } float nextDeltaX() { - incrementIndices(mdXIndices, mdXsIndex); - - int nextIndex = mdXIndex + 1; - if (nextIndex < mdXs.length) { - mdXIndex = nextIndex; - String string = mdXs[nextIndex]; - float val = PropHelper.fromRelativeToFloat(string, mWidth, 0, 1, fontSize); - mdx += val * mScale; + incrementIndices(mDXIndices, mDXsIndex); + + int nextIndex = mDXIndex + 1; + if (nextIndex < mDXs.length) { + mDXIndex = nextIndex; + String string = mDXs[nextIndex]; + float val = PropHelper.fromRelativeToFloat(string, mWidth, 0, 1, getFontSize()); + mDX += val * mScale; } - return mdx; + return mDX; } float nextDeltaY() { - incrementIndices(mdYIndices, mdYsIndex); - - int nextIndex = mdYIndex + 1; - if (nextIndex < mdYs.length) { - mdYIndex = nextIndex; - String string = mdYs[nextIndex]; - float val = PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, fontSize); - mdy += val * mScale; + incrementIndices(mDYIndices, mDYsIndex); + + int nextIndex = mDYIndex + 1; + if (nextIndex < mDYs.length) { + mDYIndex = nextIndex; + String string = mDYs[nextIndex]; + float val = PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, getFontSize()); + mDY += val * mScale; } - return mdy; + return mDY; } float nextRotation() { @@ -356,25 +397,9 @@ float getHeight() { return mHeight; } - double getFontSize() { - for (int index = top; index >= 0; index--) { - ReadableMap font = mFontContext.get(index); - - if (mFontContext.get(index).hasKey(FONT_SIZE)) { - return font.getDouble(FONT_SIZE); - } - } - - if (top > -1) { - return mNodes.get(0).getFontSizeFromParentContext(); - } - - return DEFAULT_FONT_SIZE; - } - ReadableMap getFont() { WritableMap map = Arguments.createMap(); - map.putDouble(FONT_SIZE, fontSize); + map.putDouble(FONT_SIZE, getFontSize()); boolean letterSpacingSet = false; boolean fontFamilySet = false; @@ -382,7 +407,7 @@ ReadableMap getFont() { boolean fontStyleSet = false; boolean kerningSet = false; - for (int index = top; index >= 0; index--) { + for (int index = mTop; index >= 0; index--) { ReadableMap font = mFontContext.get(index); if (!letterSpacingSet && font.hasKey(LETTER_SPACING)) { @@ -424,23 +449,4 @@ ReadableMap getFont() { return map; } - - private String[] getStringArrayFromReadableArray(ReadableArray readableArray) { - int size = readableArray.size(); - String[] strings = new String[size]; - for (int i = 0; i < size; i++) { - strings[i] = readableArray.getString(i); - } - return strings; - } - - private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { - int size = readableArray.size(); - float[] floats = new float[size]; - for (int i = 0; i < size; i++) { - String string = readableArray.getString(i); - floats[i] = Float.valueOf(string); - } - return floats; - } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index b85930abd..113d4acc2 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -41,9 +41,9 @@ class TextShadowNode extends GroupShadowNode { private int mTextDecoration = TEXT_DECORATION_NONE; private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; + private @Nullable ReadableArray mRotate; private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaY; - private @Nullable ReadableArray mRotate; @ReactProp(name = "textAnchor") public void setTextAnchor(int textAnchor) { diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 989ea3207..12407fb6e 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -44,8 +44,8 @@ abstract class VirtualNode extends LayoutShadowNode { 0, 0, 1 }; float mOpacity = 1f; - private double mFontSize = -1; - private double mParentFontSize = -1; + private float mFontSize = -1; + private float mParentFontSize = -1; Matrix mMatrix = new Matrix(); private int mClipRule; @@ -136,7 +136,7 @@ private GroupShadowNode getTextRoot(Class shadowNodeClass) { return mTextRoot; } - double getFontSizeFromContext() { + float getFontSizeFromContext() { if (mFontSize != -1) { return mFontSize; } @@ -149,7 +149,7 @@ private GroupShadowNode getTextRoot(Class shadowNodeClass) { return mFontSize; } - double getFontSizeFromParentContext() { + float getFontSizeFromParentContext() { if (mParentFontSize != -1) { return mParentFontSize; } diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 3eba521a6..9b6db3d3d 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import React, {Children} from 'react'; import TSpan from '../../elements/TSpan'; -const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm%]*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i; +const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?[ptexm%])*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i; const fontFamilyPrefix = /^[\s"']*/; const fontFamilySuffix = /[\s"']*$/; const spaceReg = /\s+/; @@ -42,12 +42,12 @@ function parseFontString(font) { return null; } let fontFamily = extractSingleFontFamily(match[3]); - let fontSize = +match[2] || 12; + let fontSize = match[2] || "12"; let isBold = /bold/.exec(match[1]); let isItalic = /italic/.exec(match[1]); cachedFontObjectsFromString[font] = { - fontFamily: fontFamily, - fontSize: fontSize, + fontSize, + fontFamily, fontWeight: isBold ? 'bold' : 'normal', fontStyle: isItalic ? 'italic' : 'normal' }; @@ -56,15 +56,14 @@ function parseFontString(font) { export function extractFont(props) { let font = props.font; - let fontSize = +props.fontSize; let ownedFont = { fontFamily: extractSingleFontFamily(props.fontFamily), - fontSize: isNaN(fontSize) ? null : fontSize, + letterSpacing: props.letterSpacing, fontWeight: props.fontWeight, fontStyle: props.fontStyle, + fontSize: props.fontSize, kerning: props.kerning, - letterSpacing: props.letterSpacing, }; if (typeof props.font === 'string') { From 147c4fdf7dda67ac50c6e1408ba2b481d94f0c9f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 22:23:44 +0300 Subject: [PATCH 096/198] Add documentation for getFontSize (relative font sizes work now.) --- .../java/com/horcrux/svg/GlyphContext.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 13f23ccaf..28d9fd072 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -305,6 +305,7 @@ private float getActualFontSize() { if (mFontContext.get(index).hasKey(FONT_SIZE)) { String string = font.getString(FONT_SIZE); + // https://www.w3.org/TR/SVG11/text.html#FontSizeProperty return PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, DEFAULT_FONT_SIZE); } } @@ -316,6 +317,28 @@ private float getActualFontSize() { return DEFAULT_FONT_SIZE; } + /** + * Get font size from context. + * + * ‘font-size’ : | | | | inherit + * + * 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 is provided without a unit identifier + * (e.g., an unqualified number such as 128), the SVG user agent + * processes the as a height value in the current user + * coordinate system. + * + * If a is provided with one of the unit identifiers + * (e.g., 12pt or 10%), then the SVG user agent converts the + * 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). + * */ float getFontSize() { if (mFontSize == -1) { mFontSize = getActualFontSize(); From eaf7b5e88b41ce81528cefd84b01ef4d4b88bd57 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 22 Jul 2017 23:02:37 +0300 Subject: [PATCH 097/198] Improve fontSize handling --- .../java/com/horcrux/svg/GlyphContext.java | 31 ++++++++++++++----- lib/extract/extractText.js | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 28d9fd072..931229889 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -299,28 +299,43 @@ private static void incrementIndices(ArrayList indices, int topIndex) { } } + // https://www.w3.org/TR/SVG11/text.html#FontSizeProperty private float getActualFontSize() { + // TODO Should handle an ancestor hierarchy of relative fontSize until default in the root + float fontSizeFromParentContext = mTop > -1 ? + mNodes.get(0).getFontSizeFromParentContext() : DEFAULT_FONT_SIZE; + + // TODO consider relative fontSizes in this glyph context stack as well + // TODO Should parhaps calculate relative size on pushContext for (int index = mTop; index >= 0; index--) { ReadableMap font = mFontContext.get(index); if (mFontContext.get(index).hasKey(FONT_SIZE)) { String string = font.getString(FONT_SIZE); - // https://www.w3.org/TR/SVG11/text.html#FontSizeProperty - return PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, DEFAULT_FONT_SIZE); + return PropHelper.fromRelativeToFloat( + string, + fontSizeFromParentContext, + 0, + 1, + fontSizeFromParentContext + ); } } - if (mTop > -1) { - return mNodes.get(0).getFontSizeFromParentContext(); - } - - return DEFAULT_FONT_SIZE; + return fontSizeFromParentContext; } /** * Get font size from context. * - * ‘font-size’ : | | | | inherit + * ‘font-size’ + * Value: | | | | inherit + * Initial: medium + * Applies to: text content elements + * Inherited: yes, the computed value is inherited + * Percentages: refer to parent element's font size TODO + * 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 diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 9b6db3d3d..4530cc841 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -62,7 +62,7 @@ export function extractFont(props) { letterSpacing: props.letterSpacing, fontWeight: props.fontWeight, fontStyle: props.fontStyle, - fontSize: props.fontSize, + fontSize: props.fontSize ? '' + props.fontSize : null, kerning: props.kerning, }; From 0b9fb6fb61dabce6b8bd17f7b805cd0d3a599827 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 00:26:41 +0300 Subject: [PATCH 098/198] Finish implementing font-size Percentages: refer to parent element's font size. --- .../java/com/horcrux/svg/GlyphContext.java | 193 ++++++++++-------- .../java/com/horcrux/svg/VirtualNode.java | 2 +- 2 files changed, 106 insertions(+), 89 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 931229889..284dcf204 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -29,6 +29,12 @@ class GlyphContext { private static final String FONT_FAMILY = "fontFamily"; private static final String LETTER_SPACING = "letterSpacing"; + // Empty font context map + private static final WritableMap DEFAULT_MAP = Arguments.createMap(); + static { + DEFAULT_MAP.putDouble(FONT_SIZE, DEFAULT_FONT_SIZE); + } + // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); private final ArrayList mYsContext = new ArrayList<>(); @@ -52,7 +58,6 @@ class GlyphContext { // Current stack (one per node push/pop) private final ArrayList mFontContext = new ArrayList<>(); - private final ArrayList mNodes = new ArrayList<>(); // Cleared on push context, cached on getFontSize private float mFontSize = DEFAULT_FONT_SIZE; @@ -149,10 +154,104 @@ private void reset() { mX = mY = mDX = mDY = 0; } + ReadableMap getFont() { + if (mTop >= 0) { + return mFontContext.get(mTop); + } else { + return DEFAULT_MAP; + } + } + + private ReadableMap getTopOrParentFont(GroupShadowNode child) { + if (mTop >= 0) { + return mFontContext.get(mTop); + } else { + GroupShadowNode parentRoot = child.getParentTextRoot(); + if (parentRoot != null) { + return parentRoot.getGlyphContext().getFont(); + } else { + return DEFAULT_MAP; + } + } + } + + private WritableMap mergeFont(GroupShadowNode node, @Nullable ReadableMap font) { + ReadableMap parent = getTopOrParentFont(node); + WritableMap map = Arguments.createMap(); + + if (parent.hasKey(LETTER_SPACING)) { + map.putDouble(LETTER_SPACING, parent.getDouble(LETTER_SPACING)); + } + + if (parent.hasKey(FONT_FAMILY)) { + map.putString(FONT_FAMILY, parent.getString(FONT_FAMILY)); + } + + if (parent.hasKey(FONT_WEIGHT)) { + map.putString(FONT_WEIGHT, parent.getString(FONT_WEIGHT)); + } + + if (parent.hasKey(FONT_STYLE)) { + map.putString(FONT_STYLE, parent.getString(FONT_STYLE)); + } + + if (parent.hasKey(KERNING)) { + map.putDouble(KERNING, parent.getDouble(KERNING)); + } + + float parentFontSize = (float) parent.getDouble(FONT_SIZE); + map.putDouble(FONT_SIZE, parentFontSize); + mFontSize = parentFontSize; + + if (font == null) { + return map; + } + + if (font.hasKey(LETTER_SPACING)) { + String letterSpacingString = font.getString(LETTER_SPACING); + float letterSpacing = Float.valueOf(letterSpacingString); + map.putDouble(LETTER_SPACING, letterSpacing); + } + + if (font.hasKey(FONT_FAMILY)) { + String fontFamily = font.getString(FONT_FAMILY); + map.putString(FONT_FAMILY, fontFamily); + } + + if (font.hasKey(FONT_WEIGHT)) { + String fontWeight = font.getString(FONT_WEIGHT); + map.putString(FONT_WEIGHT, fontWeight); + } + + if (font.hasKey(FONT_STYLE)) { + String fontStyle = font.getString(FONT_STYLE); + map.putString(FONT_STYLE, fontStyle); + } + + if (font.hasKey(KERNING)) { + String kerningString = font.getString(KERNING); + float kerning = Float.valueOf(kerningString); + map.putDouble(KERNING, kerning); + } + + if (font.hasKey(FONT_SIZE)) { + String string = font.getString(FONT_SIZE); + float value = PropHelper.fromRelativeToFloat( + string, + parentFontSize, + 0, + 1, + parentFontSize + ); + map.putDouble(FONT_SIZE, value); + mFontSize = value; + } + + return map; + } + private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { - mFontContext.add(font); - mNodes.add(node); - mFontSize = -1; + mFontContext.add(mergeFont(node, font)); mIndexTop++; mTop++; } @@ -248,7 +347,6 @@ void popContext() { mRsIndices.remove(mIndexTop); mFontContext.remove(mTop); - mNodes.remove(mTop); mIndexTop--; mTop--; @@ -300,31 +398,6 @@ private static void incrementIndices(ArrayList indices, int topIndex) { } // https://www.w3.org/TR/SVG11/text.html#FontSizeProperty - private float getActualFontSize() { - // TODO Should handle an ancestor hierarchy of relative fontSize until default in the root - float fontSizeFromParentContext = mTop > -1 ? - mNodes.get(0).getFontSizeFromParentContext() : DEFAULT_FONT_SIZE; - - // TODO consider relative fontSizes in this glyph context stack as well - // TODO Should parhaps calculate relative size on pushContext - for (int index = mTop; index >= 0; index--) { - ReadableMap font = mFontContext.get(index); - - if (mFontContext.get(index).hasKey(FONT_SIZE)) { - String string = font.getString(FONT_SIZE); - return PropHelper.fromRelativeToFloat( - string, - fontSizeFromParentContext, - 0, - 1, - fontSizeFromParentContext - ); - } - } - - return fontSizeFromParentContext; - } - /** * Get font size from context. * @@ -333,12 +406,12 @@ private float getActualFontSize() { * Initial: medium * Applies to: text content elements * Inherited: yes, the computed value is inherited - * Percentages: refer to parent element's font size TODO + * 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 + * baseline when multiple lines of text are set solid in a multiline * layout environment. * * For SVG, if a is provided without a unit identifier @@ -355,9 +428,6 @@ private float getActualFontSize() { * the normative definition of the property is in CSS2 ([CSS2], section 15.2.4). * */ float getFontSize() { - if (mFontSize == -1) { - mFontSize = getActualFontSize(); - } return mFontSize; } @@ -434,57 +504,4 @@ float getWidth() { float getHeight() { return mHeight; } - - ReadableMap getFont() { - WritableMap map = Arguments.createMap(); - map.putDouble(FONT_SIZE, getFontSize()); - - boolean letterSpacingSet = false; - boolean fontFamilySet = false; - boolean fontWeightSet = false; - boolean fontStyleSet = false; - boolean kerningSet = false; - - for (int index = mTop; index >= 0; index--) { - ReadableMap font = mFontContext.get(index); - - if (!letterSpacingSet && font.hasKey(LETTER_SPACING)) { - String letterSpacingString = font.getString(LETTER_SPACING); - float letterSpacing = Float.valueOf(letterSpacingString); - map.putDouble(LETTER_SPACING, letterSpacing); - letterSpacingSet = true; - } - - if (!fontFamilySet && font.hasKey(FONT_FAMILY)) { - String fontFamily = font.getString(FONT_FAMILY); - map.putString(FONT_FAMILY, fontFamily); - fontFamilySet = true; - } - - if (!fontWeightSet && font.hasKey(FONT_WEIGHT)) { - String fontWeight = font.getString(FONT_WEIGHT); - map.putString(FONT_WEIGHT, fontWeight); - fontWeightSet = true; - } - - if (!fontStyleSet && font.hasKey(FONT_STYLE)) { - String fontStyle = font.getString(FONT_STYLE); - map.putString(FONT_STYLE, fontStyle); - fontStyleSet = true; - } - - if (!kerningSet && font.hasKey(KERNING)) { - String kerningString = font.getString(KERNING); - float kerning = Float.valueOf(kerningString); - map.putDouble(KERNING, kerning); - kerningSet = true; - } - - if (letterSpacingSet && fontFamilySet && fontWeightSet && fontStyleSet && kerningSet) { - break; - } - } - - return map; - } } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 12407fb6e..e8bd08b1c 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -82,7 +82,7 @@ GroupShadowNode getTextRoot() { return shadowNode; } - private GroupShadowNode getParentTextRoot() { + GroupShadowNode getParentTextRoot() { GroupShadowNode shadowNode = getParentTextRoot(GroupShadowNode.class); if (shadowNode == null) { return getParentTextRoot(TextShadowNode.class); From 5ebcdb5a7fe335ba2326092379bf10c1de91e0e0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 01:16:47 +0300 Subject: [PATCH 099/198] Fix getTopOrParentFont; Parent GlyphContext inheritance / resolution logic. --- .../java/com/horcrux/svg/GlyphContext.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 284dcf204..7aad82db3 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -35,6 +35,9 @@ class GlyphContext { DEFAULT_MAP.putDouble(FONT_SIZE, DEFAULT_FONT_SIZE); } + // Current stack (one per node push/pop) + private final ArrayList mFontContext = new ArrayList<>(); + // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); private final ArrayList mYsContext = new ArrayList<>(); @@ -56,9 +59,6 @@ class GlyphContext { private final ArrayList mDYsIndices = new ArrayList<>(); private final ArrayList mRsIndices = new ArrayList<>(); - // Current stack (one per node push/pop) - private final ArrayList mFontContext = new ArrayList<>(); - // Cleared on push context, cached on getFontSize private float mFontSize = DEFAULT_FONT_SIZE; @@ -113,7 +113,6 @@ class GlyphContext { // Stack length and last index private int mIndexTop; - private int mTop = -1; // Constructor parameters private final float mScale; @@ -133,6 +132,8 @@ private void pushIndices() { mWidth = width; mHeight = height; + mFontContext.add(DEFAULT_MAP); + mXsContext.add(mXs); mYsContext.add(mYs); mDXsContext.add(mDXs); @@ -155,23 +156,24 @@ private void reset() { } ReadableMap getFont() { - if (mTop >= 0) { - return mFontContext.get(mTop); - } else { - return DEFAULT_MAP; - } + return mFontContext.get(mIndexTop); } private ReadableMap getTopOrParentFont(GroupShadowNode child) { - if (mTop >= 0) { - return mFontContext.get(mTop); + if (mIndexTop > 0) { + return mFontContext.get(mIndexTop); } else { GroupShadowNode parentRoot = child.getParentTextRoot(); - if (parentRoot != null) { - return parentRoot.getGlyphContext().getFont(); - } else { - return DEFAULT_MAP; + + while (parentRoot != null) { + ReadableMap map = parentRoot.getGlyphContext().getFont(); + if (map != DEFAULT_MAP) { + return map; + } + parentRoot = parentRoot.getParentTextRoot(); } + + return DEFAULT_MAP; } } @@ -253,7 +255,6 @@ private WritableMap mergeFont(GroupShadowNode node, @Nullable ReadableMap font) private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { mFontContext.add(mergeFont(node, font)); mIndexTop++; - mTop++; } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { @@ -340,16 +341,14 @@ void pushContext( } void popContext() { + mFontContext.remove(mIndexTop); mXsIndices.remove(mIndexTop); mYsIndices.remove(mIndexTop); mDXsIndices.remove(mIndexTop); mDYsIndices.remove(mIndexTop); mRsIndices.remove(mIndexTop); - mFontContext.remove(mTop); - mIndexTop--; - mTop--; int x = mXsIndex; int y = mYsIndex; From d3d460298f14616ec60324f5470584c3d966f902 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 01:53:57 +0300 Subject: [PATCH 100/198] Fix comments, rename mIndexTop to mTop. --- .../java/com/horcrux/svg/GlyphContext.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 7aad82db3..952184dc5 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -59,7 +59,7 @@ class GlyphContext { private final ArrayList mDYsIndices = new ArrayList<>(); private final ArrayList mRsIndices = new ArrayList<>(); - // Cleared on push context, cached on getFontSize + // Calculated on push context, percentage and em length depends on parent font size private float mFontSize = DEFAULT_FONT_SIZE; // Current accumulated values @@ -111,8 +111,8 @@ class GlyphContext { private int mDYIndex = -1; private int mRIndex = -1; - // Stack length and last index - private int mIndexTop; + // Top index of stack + private int mTop; // Constructor parameters private final float mScale; @@ -156,12 +156,12 @@ private void reset() { } ReadableMap getFont() { - return mFontContext.get(mIndexTop); + return mFontContext.get(mTop); } private ReadableMap getTopOrParentFont(GroupShadowNode child) { - if (mIndexTop > 0) { - return mFontContext.get(mIndexTop); + if (mTop > 0) { + return mFontContext.get(mTop); } else { GroupShadowNode parentRoot = child.getParentTextRoot(); @@ -254,7 +254,7 @@ private WritableMap mergeFont(GroupShadowNode node, @Nullable ReadableMap font) private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { mFontContext.add(mergeFont(node, font)); - mIndexTop++; + mTop++; } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { @@ -341,14 +341,14 @@ void pushContext( } void popContext() { - mFontContext.remove(mIndexTop); - mXsIndices.remove(mIndexTop); - mYsIndices.remove(mIndexTop); - mDXsIndices.remove(mIndexTop); - mDYsIndices.remove(mIndexTop); - mRsIndices.remove(mIndexTop); + mFontContext.remove(mTop); + mXsIndices.remove(mTop); + mYsIndices.remove(mTop); + mDXsIndices.remove(mTop); + mDYsIndices.remove(mTop); + mRsIndices.remove(mTop); - mIndexTop--; + mTop--; int x = mXsIndex; int y = mYsIndex; @@ -356,11 +356,11 @@ void popContext() { int dy = mDYsIndex; int r = mRsIndex; - mXsIndex = mXsIndices.get(mIndexTop); - mYsIndex = mYsIndices.get(mIndexTop); - mDXsIndex = mDXsIndices.get(mIndexTop); - mDYsIndex = mDYsIndices.get(mIndexTop); - mRsIndex = mRsIndices.get(mIndexTop); + mXsIndex = mXsIndices.get(mTop); + mYsIndex = mYsIndices.get(mTop); + mDXsIndex = mDXsIndices.get(mTop); + mDYsIndex = mDYsIndices.get(mTop); + mRsIndex = mRsIndices.get(mTop); if (x != mXsIndex) { mXsContext.remove(x); From 9f954079dadcde17d8fe60c217ae5814c749f518 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 02:11:08 +0300 Subject: [PATCH 101/198] Cache topFont --- .../java/com/horcrux/svg/GlyphContext.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 952184dc5..076296207 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -31,6 +31,7 @@ class GlyphContext { // Empty font context map private static final WritableMap DEFAULT_MAP = Arguments.createMap(); + static { DEFAULT_MAP.putDouble(FONT_SIZE, DEFAULT_FONT_SIZE); } @@ -61,6 +62,7 @@ class GlyphContext { // Calculated on push context, percentage and em length depends on parent font size private float mFontSize = DEFAULT_FONT_SIZE; + private ReadableMap topFont = DEFAULT_MAP; // Current accumulated values // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate @@ -156,12 +158,12 @@ private void reset() { } ReadableMap getFont() { - return mFontContext.get(mTop); + return topFont; } private ReadableMap getTopOrParentFont(GroupShadowNode child) { if (mTop > 0) { - return mFontContext.get(mTop); + return topFont; } else { GroupShadowNode parentRoot = child.getParentTextRoot(); @@ -177,9 +179,12 @@ private ReadableMap getTopOrParentFont(GroupShadowNode child) { } } - private WritableMap mergeFont(GroupShadowNode node, @Nullable ReadableMap font) { + private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { ReadableMap parent = getTopOrParentFont(node); WritableMap map = Arguments.createMap(); + mFontContext.add(map); + topFont = map; + mTop++; if (parent.hasKey(LETTER_SPACING)) { map.putDouble(LETTER_SPACING, parent.getDouble(LETTER_SPACING)); @@ -206,7 +211,7 @@ private WritableMap mergeFont(GroupShadowNode node, @Nullable ReadableMap font) mFontSize = parentFontSize; if (font == null) { - return map; + return; } if (font.hasKey(LETTER_SPACING)) { @@ -248,13 +253,6 @@ private WritableMap mergeFont(GroupShadowNode node, @Nullable ReadableMap font) map.putDouble(FONT_SIZE, value); mFontSize = value; } - - return map; - } - - private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { - mFontContext.add(mergeFont(node, font)); - mTop++; } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { @@ -356,6 +354,7 @@ void popContext() { int dy = mDYsIndex; int r = mRsIndex; + topFont = mFontContext.get(mTop); mXsIndex = mXsIndices.get(mTop); mYsIndex = mYsIndices.get(mTop); mDXsIndex = mDXsIndices.get(mTop); @@ -397,35 +396,36 @@ private static void incrementIndices(ArrayList indices, int topIndex) { } // https://www.w3.org/TR/SVG11/text.html#FontSizeProperty + /** * Get font size from context. - * + *

* ‘font-size’ - * Value: | | | | 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 - * + * Value: | | | | 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 is provided without a unit identifier * (e.g., an unqualified number such as 128), the SVG user agent * processes the as a height value in the current user * coordinate system. - * + *

* If a is provided with one of the unit identifiers * (e.g., 12pt or 10%), then the SVG user agent converts the * 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). - * */ + */ float getFontSize() { return mFontSize; } From d8afd7770f9e3a4cdbf0b0f922007c418486c47c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 02:14:42 +0300 Subject: [PATCH 102/198] Fix code completion documentation rendering --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 076296207..15f3deaba 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -401,7 +401,7 @@ private static void incrementIndices(ArrayList indices, int topIndex) { * Get font size from context. *

* ‘font-size’ - * Value: | | | | inherit + * Value: < absolute-size > | < relative-size > | < length > | < percentage > | inherit * Initial: medium * Applies to: text content elements * Inherited: yes, the computed value is inherited @@ -413,14 +413,14 @@ private static void incrementIndices(ArrayList indices, int topIndex) { * baseline when multiple lines of text are set solid in a multiline * layout environment. *

- * For SVG, if a is provided without a unit identifier + * 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 as a height value in the current user + * processes the < length > as a height value in the current user * coordinate system. *

- * If a is provided with one of the unit identifiers + * If a < length > is provided with one of the unit identifiers * (e.g., 12pt or 10%), then the SVG user agent converts the - * into a corresponding value in the current user + * < 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, From 5b78c93c40983c90eb44314e679c5c61fd93d2bb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 03:30:10 +0300 Subject: [PATCH 103/198] Use mFontSize instead of getFontSize() --- .../main/java/com/horcrux/svg/GlyphContext.java | 16 ++++++++-------- .../java/com/horcrux/svg/TextShadowNode.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 15f3deaba..62736d30b 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -285,9 +285,9 @@ void pushContext( @Nullable ReadableMap font, @Nullable ReadableArray x, @Nullable ReadableArray y, - @Nullable ReadableArray rotate, @Nullable ReadableArray deltaX, - @Nullable ReadableArray deltaY + @Nullable ReadableArray deltaY, + @Nullable ReadableArray rotate ) { if (reset) { this.reset(); @@ -437,8 +437,8 @@ float nextX(float glyphWidth) { if (nextIndex < mXs.length) { mDX = 0; mXIndex = nextIndex; - String val = mXs[nextIndex]; - mX = PropHelper.fromRelativeToFloat(val, mWidth, 0, mScale, getFontSize()); + String string = mXs[nextIndex]; + mX = PropHelper.fromRelativeToFloat(string, mWidth, 0, mScale, mFontSize); } mX += glyphWidth; @@ -453,8 +453,8 @@ float nextY() { if (nextIndex < mYs.length) { mDY = 0; mYIndex = nextIndex; - String val = mYs[nextIndex]; - mY = PropHelper.fromRelativeToFloat(val, mHeight, 0, mScale, getFontSize()); + String string = mYs[nextIndex]; + mY = PropHelper.fromRelativeToFloat(string, mHeight, 0, mScale, mFontSize); } return mY; @@ -467,7 +467,7 @@ float nextDeltaX() { if (nextIndex < mDXs.length) { mDXIndex = nextIndex; String string = mDXs[nextIndex]; - float val = PropHelper.fromRelativeToFloat(string, mWidth, 0, 1, getFontSize()); + float val = PropHelper.fromRelativeToFloat(string, mWidth, 0, 1, mFontSize); mDX += val * mScale; } @@ -481,7 +481,7 @@ float nextDeltaY() { if (nextIndex < mDYs.length) { mDYIndex = nextIndex; String string = mDYs[nextIndex]; - float val = PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, getFontSize()); + float val = PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, mFontSize); mDY += val * mScale; } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 113d4acc2..420bf7c15 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -178,6 +178,6 @@ Path getGroupPath(Canvas canvas, Paint paint) { @Override void pushGlyphContext() { boolean isTextNode = !(this instanceof TextPathShadowNode) && !(this instanceof TSpanShadowNode); - getTextRootGlyphContext().pushContext(isTextNode, this, mFont, mPositionX, mPositionY, mRotate, mDeltaX, mDeltaY); + getTextRootGlyphContext().pushContext(isTextNode, this, mFont, mPositionX, mPositionY, mDeltaX, mDeltaY, mRotate); } } From 2bb08d9f7a8a038a5795f6cacbff081689efb0c9 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 04:02:36 +0300 Subject: [PATCH 104/198] Fix signature and documentation --- .../main/java/com/horcrux/svg/PropHelper.java | 37 +++++++++++++++---- .../java/com/horcrux/svg/TSpanShadowNode.java | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 0a59519b9..5f24a5975 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -82,17 +82,38 @@ static int toMatrixData(ReadableArray value, float[] sRawMatrix, float mScale) { static private final Pattern percentageRegExp = Pattern.compile("^(-?\\d+(?:\\.\\d+)?)%$"); /** - * Converts length string into actual based on a relative number - * + * Converts length string into float value + * in the current user coordinate system * * @param length length string - * @param relative relative number - * @param offset offset number + * @param relative relative size for percentages + * @param offset offset for all units * @param fontSize current font size - * @return actual float based on relative number + * @return value in the current user coordinate system */ - - static float fromRelativeToFloat(String length, float relative, float offset, float scale, double fontSize) { + static float fromRelativeToFloat(String length, float relative, float offset, float scale, float 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; @@ -112,7 +133,7 @@ static float fromRelativeToFloat(String length, float relative, float offset, fl break; case "em": - unit = (float) fontSize; + unit = fontSize; break; /* diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index f141f9c0d..a22c8cb4e 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -132,7 +132,7 @@ private Path getLinePath(String line, Paint paint) { if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - double size = getFontSizeFromContext(); + float size = gc.getFontSize(); String startOffset = textPath.getStartOffset(); offset = PropHelper.fromRelativeToFloat(startOffset, distance, 0, mScale, size); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" From 904b4096efaa1a0de9d5618f84c5221e48772289 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 04:31:14 +0300 Subject: [PATCH 105/198] Optimize pushNodeAndFont, use parent font map as shortcut if current is null. Keep all font map values as Strings until use (except font size, which needs to be absolute, for computing relative size values in inheritance). --- .../java/com/horcrux/svg/GlyphContext.java | 65 ++++++++----------- .../java/com/horcrux/svg/TSpanShadowNode.java | 4 +- 2 files changed, 28 insertions(+), 41 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 62736d30b..21980de10 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -181,66 +181,49 @@ private ReadableMap getTopOrParentFont(GroupShadowNode child) { private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { ReadableMap parent = getTopOrParentFont(node); - WritableMap map = Arguments.createMap(); - mFontContext.add(map); - topFont = map; mTop++; - if (parent.hasKey(LETTER_SPACING)) { - map.putDouble(LETTER_SPACING, parent.getDouble(LETTER_SPACING)); - } - - if (parent.hasKey(FONT_FAMILY)) { - map.putString(FONT_FAMILY, parent.getString(FONT_FAMILY)); - } - - if (parent.hasKey(FONT_WEIGHT)) { - map.putString(FONT_WEIGHT, parent.getString(FONT_WEIGHT)); - } - - if (parent.hasKey(FONT_STYLE)) { - map.putString(FONT_STYLE, parent.getString(FONT_STYLE)); - } - - if (parent.hasKey(KERNING)) { - map.putDouble(KERNING, parent.getDouble(KERNING)); - } - - float parentFontSize = (float) parent.getDouble(FONT_SIZE); - map.putDouble(FONT_SIZE, parentFontSize); - mFontSize = parentFontSize; - if (font == null) { + mFontContext.add(parent); return; } + WritableMap map = Arguments.createMap(); + mFontContext.add(map); + topFont = map; + if (font.hasKey(LETTER_SPACING)) { - String letterSpacingString = font.getString(LETTER_SPACING); - float letterSpacing = Float.valueOf(letterSpacingString); - map.putDouble(LETTER_SPACING, letterSpacing); + map.putString(LETTER_SPACING, font.getString(LETTER_SPACING)); + } else if (parent.hasKey(LETTER_SPACING)) { + map.putString(LETTER_SPACING, parent.getString(LETTER_SPACING)); } if (font.hasKey(FONT_FAMILY)) { - String fontFamily = font.getString(FONT_FAMILY); - map.putString(FONT_FAMILY, fontFamily); + map.putString(FONT_FAMILY, font.getString(FONT_FAMILY)); + } else if (parent.hasKey(FONT_FAMILY)) { + map.putString(FONT_FAMILY, parent.getString(FONT_FAMILY)); } if (font.hasKey(FONT_WEIGHT)) { - String fontWeight = font.getString(FONT_WEIGHT); - map.putString(FONT_WEIGHT, fontWeight); + map.putString(FONT_WEIGHT, font.getString(FONT_WEIGHT)); + } else if (parent.hasKey(FONT_WEIGHT)) { + map.putString(FONT_WEIGHT, parent.getString(FONT_WEIGHT)); } if (font.hasKey(FONT_STYLE)) { - String fontStyle = font.getString(FONT_STYLE); - map.putString(FONT_STYLE, fontStyle); + map.putString(FONT_STYLE, font.getString(FONT_STYLE)); + } else if (parent.hasKey(FONT_STYLE)) { + map.putString(FONT_STYLE, parent.getString(FONT_STYLE)); } if (font.hasKey(KERNING)) { - String kerningString = font.getString(KERNING); - float kerning = Float.valueOf(kerningString); - map.putDouble(KERNING, kerning); + map.putString(KERNING, font.getString(KERNING)); + } else if (parent.hasKey(KERNING)) { + map.putString(KERNING, parent.getString(KERNING)); } + float parentFontSize = (float) parent.getDouble(FONT_SIZE); + if (font.hasKey(FONT_SIZE)) { String string = font.getString(FONT_SIZE); float value = PropHelper.fromRelativeToFloat( @@ -252,7 +235,11 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { ); map.putDouble(FONT_SIZE, value); mFontSize = value; + } else { + mFontSize = parentFontSize; } + + map.putDouble(FONT_SIZE, mFontSize); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index a22c8cb4e..26a471eb6 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -161,7 +161,7 @@ private Path getLinePath(String line, Paint paint) { boolean autoKerning = true; float kerning = DEFAULT_KERNING; if (font.hasKey(PROP_KERNING)) { - kerning = (float) (font.getDouble(PROP_KERNING) * mScale); + kerning = Float.valueOf(font.getString(PROP_KERNING)) * mScale; autoKerning = false; } @@ -222,7 +222,7 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { float fontSize = (float) font.getDouble(PROP_FONT_SIZE) * mScale; float letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? - (float) font.getDouble(PROP_LETTER_SPACING) * mScale + Float.valueOf(font.getString(PROP_LETTER_SPACING)) * mScale : DEFAULT_LETTER_SPACING; boolean isBold = font.hasKey(PROP_FONT_WEIGHT) && From 45b47fe119f13ba591545f1f2c65a756932207eb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 04:39:39 +0300 Subject: [PATCH 106/198] Refactor pushNodeAndFont repeated code into static setIfExists method. --- .../java/com/horcrux/svg/GlyphContext.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 21980de10..a6c97c122 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -179,6 +179,14 @@ private ReadableMap getTopOrParentFont(GroupShadowNode child) { } } + private static void setIfExists(String key, WritableMap map, ReadableMap font, ReadableMap parent) { + if (font.hasKey(key)) { + map.putString(key, font.getString(key)); + } else if (parent.hasKey(key)) { + map.putString(key, parent.getString(key)); + } + } + private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { ReadableMap parent = getTopOrParentFont(node); mTop++; @@ -192,35 +200,15 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { mFontContext.add(map); topFont = map; - if (font.hasKey(LETTER_SPACING)) { - map.putString(LETTER_SPACING, font.getString(LETTER_SPACING)); - } else if (parent.hasKey(LETTER_SPACING)) { - map.putString(LETTER_SPACING, parent.getString(LETTER_SPACING)); - } + setIfExists(LETTER_SPACING, map, font, parent); - if (font.hasKey(FONT_FAMILY)) { - map.putString(FONT_FAMILY, font.getString(FONT_FAMILY)); - } else if (parent.hasKey(FONT_FAMILY)) { - map.putString(FONT_FAMILY, parent.getString(FONT_FAMILY)); - } + setIfExists(FONT_FAMILY, map, font, parent); - if (font.hasKey(FONT_WEIGHT)) { - map.putString(FONT_WEIGHT, font.getString(FONT_WEIGHT)); - } else if (parent.hasKey(FONT_WEIGHT)) { - map.putString(FONT_WEIGHT, parent.getString(FONT_WEIGHT)); - } + setIfExists(FONT_WEIGHT, map, font, parent); - if (font.hasKey(FONT_STYLE)) { - map.putString(FONT_STYLE, font.getString(FONT_STYLE)); - } else if (parent.hasKey(FONT_STYLE)) { - map.putString(FONT_STYLE, parent.getString(FONT_STYLE)); - } + setIfExists(FONT_STYLE, map, font, parent); - if (font.hasKey(KERNING)) { - map.putString(KERNING, font.getString(KERNING)); - } else if (parent.hasKey(KERNING)) { - map.putString(KERNING, parent.getString(KERNING)); - } + setIfExists(KERNING, map, font, parent); float parentFontSize = (float) parent.getDouble(FONT_SIZE); From 1b24650665d03216a964efe22cdcfc55f7132bc5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 04:47:12 +0300 Subject: [PATCH 107/198] Rename setIfExists to put --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index a6c97c122..e31e87b5f 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -179,7 +179,7 @@ private ReadableMap getTopOrParentFont(GroupShadowNode child) { } } - private static void setIfExists(String key, WritableMap map, ReadableMap font, ReadableMap parent) { + private static void put(String key, WritableMap map, ReadableMap font, ReadableMap parent) { if (font.hasKey(key)) { map.putString(key, font.getString(key)); } else if (parent.hasKey(key)) { @@ -200,15 +200,15 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { mFontContext.add(map); topFont = map; - setIfExists(LETTER_SPACING, map, font, parent); + put(LETTER_SPACING, map, font, parent); - setIfExists(FONT_FAMILY, map, font, parent); + put(FONT_FAMILY, map, font, parent); - setIfExists(FONT_WEIGHT, map, font, parent); + put(FONT_WEIGHT, map, font, parent); - setIfExists(FONT_STYLE, map, font, parent); + put(FONT_STYLE, map, font, parent); - setIfExists(KERNING, map, font, parent); + put(KERNING, map, font, parent); float parentFontSize = (float) parent.getDouble(FONT_SIZE); From a1aad32e0d2f90c2fb807aea3e11d574972e12f8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 05:40:24 +0300 Subject: [PATCH 108/198] High precision intermediate calculations for rendering. Use double instead of float. --- .../src/main/java/com/horcrux/svg/Brush.java | 36 +++++----- .../com/horcrux/svg/CircleShadowNode.java | 10 +-- .../com/horcrux/svg/EllipseShadowNode.java | 10 +-- .../java/com/horcrux/svg/GlyphContext.java | 50 +++++++------- .../java/com/horcrux/svg/ImageShadowNode.java | 8 +-- .../java/com/horcrux/svg/LineShadowNode.java | 12 ++-- .../main/java/com/horcrux/svg/PropHelper.java | 22 +++--- .../java/com/horcrux/svg/RectShadowNode.java | 16 ++--- .../com/horcrux/svg/RenderableShadowNode.java | 4 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 68 +++++++++---------- .../java/com/horcrux/svg/UseShadowNode.java | 2 +- .../main/java/com/horcrux/svg/ViewBox.java | 34 +++++----- .../java/com/horcrux/svg/VirtualNode.java | 32 +++------ 13 files changed, 145 insertions(+), 159 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/Brush.java b/android/src/main/java/com/horcrux/svg/Brush.java index eff89a95d..34301d2c2 100644 --- a/android/src/main/java/com/horcrux/svg/Brush.java +++ b/android/src/main/java/com/horcrux/svg/Brush.java @@ -107,16 +107,16 @@ void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity) parseGradientStops(mColors, stopsCount, stops, stopsColors, opacity); if (mType == BrushType.LINEAR_GRADIENT) { - float x1 = PropHelper.fromRelativeToFloat(mPoints.getString(0), width, offsetX, scale, paint.getTextSize()); - float y1 = PropHelper.fromRelativeToFloat(mPoints.getString(1), height, offsetY, scale, paint.getTextSize()); - float x2 = PropHelper.fromRelativeToFloat(mPoints.getString(2), width, offsetX, scale, paint.getTextSize()); - float y2 = PropHelper.fromRelativeToFloat(mPoints.getString(3), height, offsetY, scale, paint.getTextSize()); + double x1 = PropHelper.fromRelative(mPoints.getString(0), width, offsetX, scale, paint.getTextSize()); + double y1 = PropHelper.fromRelative(mPoints.getString(1), height, offsetY, scale, paint.getTextSize()); + double x2 = PropHelper.fromRelative(mPoints.getString(2), width, offsetX, scale, paint.getTextSize()); + double y2 = PropHelper.fromRelative(mPoints.getString(3), height, offsetY, scale, paint.getTextSize()); Shader linearGradient = new LinearGradient( - x1, - y1, - x2, - y2, + (float) x1, + (float) y1, + (float) x2, + (float) y2, stopsColors, stops, Shader.TileMode.CLAMP); @@ -129,24 +129,24 @@ void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity) paint.setShader(linearGradient); } else if (mType == BrushType.RADIAL_GRADIENT) { - float rx = PropHelper.fromRelativeToFloat(mPoints.getString(2), width, 0f, scale, paint.getTextSize()); - float ry = PropHelper.fromRelativeToFloat(mPoints.getString(3), height, 0f, scale, paint.getTextSize()); - float cx = PropHelper.fromRelativeToFloat(mPoints.getString(4), width, offsetX, scale, paint.getTextSize()); - float cy = PropHelper.fromRelativeToFloat(mPoints.getString(5), height, offsetY, scale, paint.getTextSize()) / (ry / rx); + double rx = PropHelper.fromRelative(mPoints.getString(2), width, 0f, scale, paint.getTextSize()); + double ry = PropHelper.fromRelative(mPoints.getString(3), height, 0f, scale, paint.getTextSize()); + double cx = PropHelper.fromRelative(mPoints.getString(4), width, offsetX, scale, paint.getTextSize()); + double cy = PropHelper.fromRelative(mPoints.getString(5), height, offsetY, scale, paint.getTextSize()) / (ry / rx); // TODO: support focus point. - //float fx = PropHelper.fromRelativeToFloat(mPoints.getString(0), width, offsetX, scale); - //float fy = PropHelper.fromRelativeToFloat(mPoints.getString(1), height, offsetY, scale) / (ry / rx); + //double fx = PropHelper.fromRelative(mPoints.getString(0), width, offsetX, scale); + //double fy = PropHelper.fromRelative(mPoints.getString(1), height, offsetY, scale) / (ry / rx); Shader radialGradient = new RadialGradient( - cx, - cy, - rx, + (float) cx, + (float) cy, + (float) rx, stopsColors, stops, Shader.TileMode.CLAMP ); Matrix radialMatrix = new Matrix(); - radialMatrix.preScale(1f, ry / rx); + radialMatrix.preScale(1f, (float) (ry / rx)); if (mMatrix != null) { radialMatrix.preConcat(mMatrix); diff --git a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java index 5d917875c..9fa449fc9 100644 --- a/android/src/main/java/com/horcrux/svg/CircleShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java @@ -46,17 +46,17 @@ public void setR(String r) { protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - float cx = relativeOnWidth(mCx); - float cy = relativeOnHeight(mCy); + double cx = relativeOnWidth(mCx); + double cy = relativeOnHeight(mCy); - float r; + double r; if (PropHelper.isPercentage(mR)) { r = relativeOnOther(mR); } else { - r = Float.parseFloat(mR) * mScale; + r = Double.parseDouble(mR) * mScale; } - path.addCircle(cx, cy, r, Path.Direction.CW); + path.addCircle((float) cx, (float) cy, (float) r, Path.Direction.CW); return path; } } diff --git a/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java b/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java index a6a982e0d..716aac483 100644 --- a/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java @@ -53,11 +53,11 @@ public void setRy(String ry) { @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - float cx = relativeOnWidth(mCx); - float cy = relativeOnHeight(mCy); - float rx = relativeOnWidth(mRx); - float ry = relativeOnHeight(mRy); - RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry); + double cx = relativeOnWidth(mCx); + double cy = relativeOnHeight(mCy); + double rx = relativeOnWidth(mRx); + double ry = relativeOnHeight(mRy); + RectF oval = new RectF((float) (cx - rx), (float) (cy - ry), (float) (cx + rx), (float) (cy + ry)); path.addOval(oval, Path.Direction.CW); return path; diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index e31e87b5f..5153187a5 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -20,7 +20,7 @@ // https://www.w3.org/TR/SVG/text.html#TSpanElement class GlyphContext { - static final float DEFAULT_FONT_SIZE = 12f; + static final double DEFAULT_FONT_SIZE = 12d; private static final String KERNING = "kerning"; private static final String FONT_SIZE = "fontSize"; @@ -44,7 +44,7 @@ class GlyphContext { private final ArrayList mYsContext = new ArrayList<>(); private final ArrayList mDXsContext = new ArrayList<>(); private final ArrayList mDYsContext = new ArrayList<>(); - private final ArrayList mRsContext = new ArrayList<>(); + private final ArrayList mRsContext = new ArrayList<>(); // Unique index into attribute list (one per unique list) private final ArrayList mXIndices = new ArrayList<>(); @@ -61,18 +61,18 @@ class GlyphContext { private final ArrayList mRsIndices = new ArrayList<>(); // Calculated on push context, percentage and em length depends on parent font size - private float mFontSize = DEFAULT_FONT_SIZE; + private double mFontSize = DEFAULT_FONT_SIZE; private ReadableMap topFont = DEFAULT_MAP; // Current accumulated values // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate // syntax is the same as that for - private float mX; - private float mY; + private double mX; + private double mY; // https://www.w3.org/TR/SVG/types.html#Length - private float mDX; - private float mDY; + private double mDX; + private double mDY; // Current SVGLengthList // https://www.w3.org/TR/SVG/types.html#InterfaceSVGLengthList @@ -97,7 +97,7 @@ class GlyphContext { // https://www.w3.org/TR/SVG/types.html#DataTypeNumbers // https://www.w3.org/TR/SVG/text.html#TSpanElementRotateAttribute - private float[] mRs = new float[]{0}; + private double[] mRs = new double[]{0}; // Current attribute list index private int mXsIndex; @@ -210,11 +210,11 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { put(KERNING, map, font, parent); - float parentFontSize = (float) parent.getDouble(FONT_SIZE); + double parentFontSize = parent.getDouble(FONT_SIZE); if (font.hasKey(FONT_SIZE)) { String string = font.getString(FONT_SIZE); - float value = PropHelper.fromRelativeToFloat( + double value = PropHelper.fromRelative( string, parentFontSize, 0, @@ -244,14 +244,14 @@ private String[] getStringArrayFromReadableArray(ReadableArray readableArray) { return strings; } - private float[] getFloatArrayFromReadableArray(ReadableArray readableArray) { + private double[] getDoubleArrayFromReadableArray(ReadableArray readableArray) { int size = readableArray.size(); - float[] floats = new float[size]; + double[] doubles = new double[size]; for (int i = 0; i < size; i++) { String string = readableArray.getString(i); - floats[i] = Float.valueOf(string); + doubles[i] = Double.valueOf(string); } - return floats; + return doubles; } void pushContext( @@ -306,7 +306,7 @@ void pushContext( mRsIndex++; mRIndex = -1; mRIndices.add(mRIndex); - mRs = getFloatArrayFromReadableArray(rotate); + mRs = getDoubleArrayFromReadableArray(rotate); mRsContext.add(mRs); } @@ -401,11 +401,11 @@ private static void incrementIndices(ArrayList indices, int topIndex) { * Except for any additional information provided in this specification, * the normative definition of the property is in CSS2 ([CSS2], section 15.2.4). */ - float getFontSize() { + double getFontSize() { return mFontSize; } - float nextX(float glyphWidth) { + double nextX(double glyphWidth) { incrementIndices(mXIndices, mXsIndex); int nextIndex = mXIndex + 1; @@ -413,7 +413,7 @@ float nextX(float glyphWidth) { mDX = 0; mXIndex = nextIndex; String string = mXs[nextIndex]; - mX = PropHelper.fromRelativeToFloat(string, mWidth, 0, mScale, mFontSize); + mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize); } mX += glyphWidth; @@ -421,7 +421,7 @@ float nextX(float glyphWidth) { return mX; } - float nextY() { + double nextY() { incrementIndices(mYIndices, mYsIndex); int nextIndex = mYIndex + 1; @@ -429,41 +429,41 @@ float nextY() { mDY = 0; mYIndex = nextIndex; String string = mYs[nextIndex]; - mY = PropHelper.fromRelativeToFloat(string, mHeight, 0, mScale, mFontSize); + mY = PropHelper.fromRelative(string, mHeight, 0, mScale, mFontSize); } return mY; } - float nextDeltaX() { + double nextDeltaX() { incrementIndices(mDXIndices, mDXsIndex); int nextIndex = mDXIndex + 1; if (nextIndex < mDXs.length) { mDXIndex = nextIndex; String string = mDXs[nextIndex]; - float val = PropHelper.fromRelativeToFloat(string, mWidth, 0, 1, mFontSize); + double val = PropHelper.fromRelative(string, mWidth, 0, 1, mFontSize); mDX += val * mScale; } return mDX; } - float nextDeltaY() { + double nextDeltaY() { incrementIndices(mDYIndices, mDYsIndex); int nextIndex = mDYIndex + 1; if (nextIndex < mDYs.length) { mDYIndex = nextIndex; String string = mDYs[nextIndex]; - float val = PropHelper.fromRelativeToFloat(string, mHeight, 0, 1, mFontSize); + double val = PropHelper.fromRelative(string, mHeight, 0, 1, mFontSize); mDY += val * mScale; } return mDY; } - float nextRotation() { + double nextRotation() { incrementIndices(mRIndices, mRsIndex); mRIndex = Math.min(mRIndex + 1, mRs.length - 1); diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index 67504a07c..0954c0ca2 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -183,10 +183,10 @@ public void onFailureImpl(DataSource dataSource) { @Nonnull private Rect getRect() { - float x = relativeOnWidth(mX); - float y = relativeOnHeight(mY); - float w = relativeOnWidth(mW); - float h = relativeOnHeight(mH); + double x = relativeOnWidth(mX); + double y = relativeOnHeight(mY); + double w = relativeOnWidth(mW); + double h = relativeOnHeight(mH); return new Rect((int) x, (int) y, (int) (x + w), (int) (y + h)); } diff --git a/android/src/main/java/com/horcrux/svg/LineShadowNode.java b/android/src/main/java/com/horcrux/svg/LineShadowNode.java index 8d134de6b..095d39e8f 100644 --- a/android/src/main/java/com/horcrux/svg/LineShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LineShadowNode.java @@ -51,13 +51,13 @@ public void setY2(String y2) { @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - float x1 = relativeOnWidth(mX1); - float y1 = relativeOnHeight(mY1); - float x2 = relativeOnWidth(mX2); - float y2 = relativeOnHeight(mY2); + double x1 = relativeOnWidth(mX1); + double y1 = relativeOnHeight(mY1); + double x2 = relativeOnWidth(mX2); + double y2 = relativeOnHeight(mY2); - path.moveTo(x1, y1); - path.lineTo(x2, y2); + path.moveTo((float) x1, (float) y1); + path.lineTo((float) x2, (float) y2); return path; } } diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 5f24a5975..6448bdbf7 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -82,16 +82,16 @@ static int toMatrixData(ReadableArray value, float[] sRawMatrix, float mScale) { static private final Pattern percentageRegExp = Pattern.compile("^(-?\\d+(?:\\.\\d+)?)%$"); /** - * Converts length string into float value + * Converts length string 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 fontSize current font size - * @return value in the current user coordinate system + * @param scale scaling parameter + * @param fontSize current font size @return value in the current user coordinate system */ - static float fromRelativeToFloat(String length, float relative, float offset, float scale, float fontSize) { + static double fromRelative(String length, double relative, double offset, double scale, double fontSize) { /* TODO list @@ -120,13 +120,13 @@ static float fromRelativeToFloat(String length, float relative, float offset, fl if (stringLength == 0) { return offset; } else if (length.codePointAt(percentIndex) == '%') { - return Float.valueOf(length.substring(0, percentIndex)) / 100 * relative + offset; + return Double.valueOf(length.substring(0, percentIndex)) / 100 * relative + offset; } else { int twoLetterUnitIndex = stringLength - 2; if (twoLetterUnitIndex > 0) { String lastTwo = length.substring(twoLetterUnitIndex); int end = twoLetterUnitIndex; - float unit = 1; + double unit = 1; switch (lastTwo) { case "px": @@ -145,7 +145,7 @@ static float fromRelativeToFloat(String length, float relative, float offset, fl */ case "pt": - unit = 1.25f; + unit = 1.25d; break; case "pc": @@ -153,11 +153,11 @@ static float fromRelativeToFloat(String length, float relative, float offset, fl break; case "mm": - unit = 3.543307f; + unit = 3.543307d; break; case "cm": - unit = 35.43307f; + unit = 35.43307d; break; case "in": @@ -168,9 +168,9 @@ static float fromRelativeToFloat(String length, float relative, float offset, fl end = stringLength; } - return Float.valueOf(length.substring(0, end)) * unit * scale + offset; + return Double.valueOf(length.substring(0, end)) * unit * scale + offset; } else { - return Float.valueOf(length) * scale + offset; + return Double.valueOf(length) * scale + offset; } } } diff --git a/android/src/main/java/com/horcrux/svg/RectShadowNode.java b/android/src/main/java/com/horcrux/svg/RectShadowNode.java index 5f75c7455..cbd27917d 100644 --- a/android/src/main/java/com/horcrux/svg/RectShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RectShadowNode.java @@ -69,12 +69,12 @@ public void setRy(String ry) { @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - float x = relativeOnWidth(mX); - float y = relativeOnHeight(mY); - float w = relativeOnWidth(mW); - float h = relativeOnHeight(mH); - float rx = relativeOnWidth(mRx); - float ry = relativeOnHeight(mRy); + double x = relativeOnWidth(mX); + double y = relativeOnHeight(mY); + double w = relativeOnWidth(mW); + double h = relativeOnHeight(mH); + double rx = relativeOnWidth(mRx); + double ry = relativeOnHeight(mRy); if (rx != 0 || ry != 0) { if (rx == 0) { @@ -90,9 +90,9 @@ protected Path getPath(Canvas canvas, Paint paint) { if (ry > h / 2) { ry = h / 2; } - path.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW); + path.addRoundRect(new RectF((float) x, (float) y, (float) (x + w), (float) (y + h)), (float) rx, (float) ry, Path.Direction.CW); } else { - path.addRect(x, y, x + w, y + h, Path.Direction.CW); + path.addRect((float) x, (float) y, (float) (x + w), (float) (y + h), Path.Direction.CW); } return path; } diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 5f6cbce42..7e5b17f49 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -236,7 +236,7 @@ private boolean setupFillPaint(Paint paint, float opacity) { */ private boolean setupStrokePaint(Paint paint, float opacity) { paint.reset(); - float strokeWidth = relativeOnOther(mStrokeWidth); + double strokeWidth = relativeOnOther(mStrokeWidth); if (strokeWidth == 0 || mStroke == null || mStroke.size() == 0) { return false; } @@ -246,7 +246,7 @@ private boolean setupStrokePaint(Paint paint, float opacity) { paint.setStrokeCap(mStrokeLinecap); paint.setStrokeJoin(mStrokeLinejoin); paint.setStrokeMiter(mStrokeMiterlimit * mScale); - paint.setStrokeWidth(strokeWidth); + paint.setStrokeWidth((float) strokeWidth); setupPaint(paint, opacity, mStroke); if (mStrokeDasharray != null && mStrokeDasharray.length > 0) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 26a471eb6..a1ded7d70 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -40,8 +40,8 @@ class TSpanShadowNode extends TextShadowNode { private static final String OTF = ".otf"; private static final String TTF = ".ttf"; - private static final float DEFAULT_KERNING = 0f; - private static final float DEFAULT_LETTER_SPACING = 0f; + private static final double DEFAULT_KERNING = 0d; + private static final double DEFAULT_LETTER_SPACING = 0d; private static final String PROP_KERNING = "kerning"; private static final String PROP_FONT_SIZE = "fontSize"; @@ -96,8 +96,8 @@ protected Path getPath(Canvas canvas, Paint paint) { return mCache; } - private float getTextAnchorShift(float width) { - float x = 0; + private double getTextAnchorShift(double width) { + double x = 0; switch (getComputedTextAnchor()) { case TEXT_ANCHOR_MIDDLE: @@ -123,18 +123,18 @@ private Path getLinePath(String line, Paint paint) { ReadableMap font = gc.getFont(); applyTextPropertiesToPaint(paint, font); - float offset = 0; - float distance = 0; - float renderMethodScaling = 1; - float textMeasure = paint.measureText(line); + double offset = 0; + double distance = 0; + double renderMethodScaling = 1; + double textMeasure = paint.measureText(line); PathMeasure pm = null; if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - float size = gc.getFontSize(); + double size = gc.getFontSize(); String startOffset = textPath.getStartOffset(); - offset = PropHelper.fromRelativeToFloat(startOffset, distance, 0, mScale, size); + offset = PropHelper.fromRelative(startOffset, distance, 0, mScale, size); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" String method = textPath.getMethod(); // method = "align | stretch" if (STRETCH.equals(method)) { @@ -144,24 +144,24 @@ private Path getLinePath(String line, Paint paint) { offset += getTextAnchorShift(textMeasure); - float x; - float y; - float r; - float dx; - float dy; + double x; + double y; + double r; + double dx; + double dy; Path glyph; - float width; + double width; Matrix matrix; String current; String previous = ""; - float previousWidth = 0; + double previousWidth = 0; char[] chars = line.toCharArray(); boolean autoKerning = true; - float kerning = DEFAULT_KERNING; + double kerning = DEFAULT_KERNING; if (font.hasKey(PROP_KERNING)) { - kerning = Float.valueOf(font.getString(PROP_KERNING)) * mScale; + kerning = Double.valueOf(font.getString(PROP_KERNING)) * mScale; autoKerning = false; } @@ -172,7 +172,7 @@ private Path getLinePath(String line, Paint paint) { width = paint.measureText(current) * renderMethodScaling; if (autoKerning) { - float both = paint.measureText(previous + current) * renderMethodScaling; + double both = paint.measureText(previous + current) * renderMethodScaling; kerning = both - previousWidth - width; previousWidth = width; previous = current; @@ -186,11 +186,11 @@ private Path getLinePath(String line, Paint paint) { matrix = new Matrix(); - float xSum = offset + x + dx - width; + double xSum = offset + x + dx - width; if (textPath != null) { - float halfway = width / 2; - float midpoint = xSum + halfway; + double halfway = width / 2; + double midpoint = xSum + halfway; if (midpoint > distance) { break; @@ -199,16 +199,16 @@ private Path getLinePath(String line, Paint paint) { } assert pm != null; - pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); + pm.getMatrix((float) midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - matrix.preTranslate(-halfway, dy); - matrix.preScale(renderMethodScaling, renderMethodScaling); - matrix.postTranslate(0, y); + matrix.preTranslate((float) -halfway, (float) dy); + matrix.preScale((float) renderMethodScaling, (float) renderMethodScaling); + matrix.postTranslate(0, (float) y); } else { - matrix.setTranslate(xSum, y + dy); + matrix.setTranslate((float) xSum, (float) (y + dy)); } - matrix.preRotate(r); + matrix.preRotate((float) r); glyph.transform(matrix); path.addPath(glyph); } @@ -219,10 +219,10 @@ private Path getLinePath(String line, Paint paint) { private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { AssetManager assetManager = getThemedContext().getResources().getAssets(); - float fontSize = (float) font.getDouble(PROP_FONT_SIZE) * mScale; + double fontSize = font.getDouble(PROP_FONT_SIZE) * mScale; - float letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? - Float.valueOf(font.getString(PROP_LETTER_SPACING)) * mScale + double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? + Double.valueOf(font.getString(PROP_LETTER_SPACING)) * mScale : DEFAULT_LETTER_SPACING; boolean isBold = font.hasKey(PROP_FONT_WEIGHT) && @@ -266,12 +266,12 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { // NB: if the font family is null / unsupported, the default one will be used paint.setTypeface(typeface); - paint.setTextSize(fontSize); + paint.setTextSize((float) fontSize); paint.setTextAlign(Paint.Align.LEFT); paint.setUnderlineText(underlineText); paint.setStrikeThruText(strikeThruText); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - paint.setLetterSpacing(letterSpacing / fontSize); + paint.setLetterSpacing((float) (letterSpacing / fontSize)); } } diff --git a/android/src/main/java/com/horcrux/svg/UseShadowNode.java b/android/src/main/java/com/horcrux/svg/UseShadowNode.java index 8eced936a..909529a10 100644 --- a/android/src/main/java/com/horcrux/svg/UseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/UseShadowNode.java @@ -58,7 +58,7 @@ public void draw(Canvas canvas, Paint paint, float opacity) { if (template instanceof SymbolShadowNode) { SymbolShadowNode symbol = (SymbolShadowNode)template; - symbol.drawSymbol(canvas, paint, opacity, relativeOnWidth(mWidth), relativeOnHeight(mHeight)); + symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mWidth), (float) relativeOnHeight(mHeight)); } else { template.draw(canvas, paint, opacity * mOpacity); } diff --git a/android/src/main/java/com/horcrux/svg/ViewBox.java b/android/src/main/java/com/horcrux/svg/ViewBox.java index 5660a1c0a..3512e8f7a 100644 --- a/android/src/main/java/com/horcrux/svg/ViewBox.java +++ b/android/src/main/java/com/horcrux/svg/ViewBox.java @@ -25,34 +25,34 @@ static Matrix getTransform(RectF vbRect, RectF eRect, String align, int meetOrSl // based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform // Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively. - float vbX = vbRect.left; - float vbY = vbRect.top; - float vbWidth = vbRect.width(); - float vbHeight = vbRect.height(); + double vbX = vbRect.left; + double vbY = vbRect.top; + double vbWidth = vbRect.width(); + double vbHeight = vbRect.height(); // Let e-x, e-y, e-width, e-height be the position and size of the element respectively. - float eX = eRect.left; - float eY = eRect.top; - float eWidth = eRect.width(); - float eHeight = eRect.height(); + double eX = eRect.left; + double eY = eRect.top; + double eWidth = eRect.width(); + double eHeight = eRect.height(); // Initialize scale-x to e-width/vb-width. - float scaleX = eWidth / vbWidth; + double scaleX = eWidth / vbWidth; // Initialize scale-y to e-height/vb-height. - float scaleY = eHeight / vbHeight; + double scaleY = eHeight / vbHeight; // Initialize translate-x to e-x - (vb-x * scale-x). // Initialize translate-y to e-y - (vb-y * scale-y). - float translateX = eX - (vbX * scaleX); - float translateY = eY - (vbY * scaleY); + double translateX = eX - (vbX * scaleX); + double translateY = eY - (vbY * scaleY); // If align is 'none' if (meetOrSlice == MOS_NONE) { // Let scale be set the smaller value of scale-x and scale-y. // Assign scale-x and scale-y to scale. - float scale = scaleX = scaleY = Math.min(scaleX, scaleY); + double scale = scaleX = scaleY = Math.min(scaleX, scaleY); // If scale is greater than 1 if (scale > 1) { @@ -76,7 +76,7 @@ static Matrix getTransform(RectF vbRect, RectF eRect, String align, int meetOrSl // If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x. if (align.contains("xMid")) { - translateX += (eWidth - vbWidth * scaleX) / 2.0f; + translateX += (eWidth - vbWidth * scaleX) / 2.0d; } // If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x. @@ -86,7 +86,7 @@ static Matrix getTransform(RectF vbRect, RectF eRect, String align, int meetOrSl // If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y. if (align.contains("YMid")) { - translateY += (eHeight - vbHeight * scaleY) / 2.0f; + translateY += (eHeight - vbHeight * scaleY) / 2.0d; } // If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y. @@ -99,8 +99,8 @@ static Matrix getTransform(RectF vbRect, RectF eRect, String align, int meetOrSl // The transform applied to content contained by the element is given by // translate(translate-x, translate-y) scale(scale-x, scale-y). Matrix transform = new Matrix(); - transform.postTranslate(translateX, translateY); - transform.preScale(scaleX, scaleY); + transform.postTranslate((float) translateX, (float) translateY); + transform.preScale((float) scaleX, (float) scaleY); return transform; } } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index e8bd08b1c..594e3f688 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -44,8 +44,7 @@ abstract class VirtualNode extends LayoutShadowNode { 0, 0, 1 }; float mOpacity = 1f; - private float mFontSize = -1; - private float mParentFontSize = -1; + private double mFontSize = -1; Matrix mMatrix = new Matrix(); private int mClipRule; @@ -136,7 +135,7 @@ private GroupShadowNode getTextRoot(Class shadowNodeClass) { return mTextRoot; } - float getFontSizeFromContext() { + private double getFontSizeFromContext() { if (mFontSize != -1) { return mFontSize; } @@ -149,19 +148,6 @@ float getFontSizeFromContext() { return mFontSize; } - float getFontSizeFromParentContext() { - if (mParentFontSize != -1) { - return mParentFontSize; - } - GroupShadowNode root = getParentTextRoot(); - if (root == null) { - mParentFontSize = DEFAULT_FONT_SIZE; - } else { - mParentFontSize = root.getGlyphContext().getFontSize(); - } - return mParentFontSize; - } - public abstract void draw(Canvas canvas, Paint paint, float opacity); /** @@ -301,19 +287,19 @@ SvgViewShadowNode getSvgShadowNode() { return mSvgShadowNode; } - float relativeOnWidth(String length) { - return PropHelper.fromRelativeToFloat(length, getCanvasWidth(), 0, mScale, getFontSizeFromContext()); + double relativeOnWidth(String length) { + return PropHelper.fromRelative(length, getCanvasWidth(), 0, mScale, getFontSizeFromContext()); } - float relativeOnHeight(String length) { - return PropHelper.fromRelativeToFloat(length, getCanvasHeight(), 0, mScale, getFontSizeFromContext()); + double relativeOnHeight(String length) { + return PropHelper.fromRelative(length, getCanvasHeight(), 0, mScale, getFontSizeFromContext()); } - float relativeOnOther(String length) { + double relativeOnOther(String length) { double powX = Math.pow((getCanvasWidth()), 2); double powY = Math.pow((getCanvasHeight()), 2); - float r = (float) (Math.sqrt(powX + powY) * M_SQRT1_2l); - return PropHelper.fromRelativeToFloat(length, r, 0, mScale, getFontSizeFromContext()); + double r = Math.sqrt(powX + powY) * M_SQRT1_2l; + return PropHelper.fromRelative(length, r, 0, mScale, getFontSizeFromContext()); } private float getCanvasWidth() { From f2352be2f590f292981090860d27069874b0d07f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 06:38:18 +0300 Subject: [PATCH 109/198] Implement correct letterSpacing --- .../java/com/horcrux/svg/GlyphContext.java | 40 ++++++++++++++----- .../java/com/horcrux/svg/TSpanShadowNode.java | 22 ++++------ 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 5153187a5..9bac2a580 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -187,6 +187,22 @@ private static void put(String key, WritableMap map, ReadableMap font, ReadableM } } + private static void putD(String key, WritableMap map, ReadableMap font, ReadableMap parent, double fontSize) { + if (font.hasKey(key)) { + String string = font.getString(key); + double value = PropHelper.fromRelative( + string, + 0, + 0, + 1, + fontSize + ); + map.putDouble(key, value); + } else if (parent.hasKey(key)) { + map.putDouble(key, parent.getDouble(key)); + } + } + private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { ReadableMap parent = getTopOrParentFont(node); mTop++; @@ -200,16 +216,6 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { mFontContext.add(map); topFont = map; - put(LETTER_SPACING, map, font, parent); - - put(FONT_FAMILY, map, font, parent); - - put(FONT_WEIGHT, map, font, parent); - - put(FONT_STYLE, map, font, parent); - - put(KERNING, map, font, parent); - double parentFontSize = parent.getDouble(FONT_SIZE); if (font.hasKey(FONT_SIZE)) { @@ -228,6 +234,20 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { } map.putDouble(FONT_SIZE, mFontSize); + + put(FONT_FAMILY, map, font, parent); + + put(FONT_WEIGHT, map, font, parent); + + put(FONT_STYLE, map, font, parent); + + // TODO https://www.w3.org/TR/SVG11/text.html#SpacingProperties + // https://drafts.csswg.org/css-text-3/#spacing + // calculated values for units in: kerning, letter-spacing and word-spacing + + putD(LETTER_SPACING, map, font, parent, mFontSize); + + putD(KERNING, map, font, parent, mFontSize); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index a1ded7d70..8afa35e73 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -158,12 +158,13 @@ private Path getLinePath(String line, Paint paint) { double previousWidth = 0; char[] chars = line.toCharArray(); - boolean autoKerning = true; - double kerning = DEFAULT_KERNING; - if (font.hasKey(PROP_KERNING)) { - kerning = Double.valueOf(font.getString(PROP_KERNING)) * mScale; - autoKerning = false; - } + boolean hasKerning = font.hasKey(PROP_KERNING); + double kerning = hasKerning ? font.getDouble(PROP_KERNING) * mScale : DEFAULT_KERNING; + boolean autoKerning = !hasKerning; + + double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? + font.getDouble(PROP_LETTER_SPACING) * mScale + : DEFAULT_LETTER_SPACING; for (int index = 0; index < length; index++) { glyph = new Path(); @@ -178,7 +179,7 @@ private Path getLinePath(String line, Paint paint) { previous = current; } - x = gc.nextX(width + kerning); + x = gc.nextX(width + kerning + letterSpacing); y = gc.nextY(); dx = gc.nextDeltaX(); dy = gc.nextDeltaY(); @@ -221,10 +222,6 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { double fontSize = font.getDouble(PROP_FONT_SIZE) * mScale; - double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? - Double.valueOf(font.getString(PROP_LETTER_SPACING)) * mScale - : DEFAULT_LETTER_SPACING; - boolean isBold = font.hasKey(PROP_FONT_WEIGHT) && BOLD.equals(font.getString(PROP_FONT_WEIGHT)); @@ -270,9 +267,6 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { paint.setTextAlign(Paint.Align.LEFT); paint.setUnderlineText(underlineText); paint.setStrikeThruText(strikeThruText); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - paint.setLetterSpacing((float) (letterSpacing / fontSize)); - } } private void setupTextPath() { From 8f62650bacf5b3472c1985ccdb7792915365d11f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 07:19:46 +0300 Subject: [PATCH 110/198] Implement wordSpacing --- .../java/com/horcrux/svg/GlyphContext.java | 9 +++-- .../java/com/horcrux/svg/TSpanShadowNode.java | 34 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 9bac2a580..18eed2b06 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -27,6 +27,7 @@ class GlyphContext { private static final String FONT_STYLE = "fontStyle"; private static final String FONT_WEIGHT = "fontWeight"; private static final String FONT_FAMILY = "fontFamily"; + private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; // Empty font context map @@ -241,13 +242,15 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { put(FONT_STYLE, map, font, parent); - // TODO https://www.w3.org/TR/SVG11/text.html#SpacingProperties + // https://www.w3.org/TR/SVG11/text.html#SpacingProperties // https://drafts.csswg.org/css-text-3/#spacing // calculated values for units in: kerning, letter-spacing and word-spacing - putD(LETTER_SPACING, map, font, parent, mFontSize); - putD(KERNING, map, font, parent, mFontSize); + + putD(WORD_SPACING, map, font, parent, mFontSize); + + putD(LETTER_SPACING, map, font, parent, mFontSize); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 8afa35e73..06aa8a4cb 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -41,6 +41,7 @@ class TSpanShadowNode extends TextShadowNode { private static final String TTF = ".ttf"; private static final double DEFAULT_KERNING = 0d; + private static final double DEFAULT_WORD_SPACING = 0d; private static final double DEFAULT_LETTER_SPACING = 0d; private static final String PROP_KERNING = "kerning"; @@ -48,6 +49,7 @@ class TSpanShadowNode extends TextShadowNode { private static final String PROP_FONT_STYLE = "fontStyle"; private static final String PROP_FONT_WEIGHT = "fontWeight"; private static final String PROP_FONT_FAMILY = "fontFamily"; + private static final String PROP_WORD_SPACING = "wordSpacing"; private static final String PROP_LETTER_SPACING = "letterSpacing"; private Path mCache; @@ -158,17 +160,42 @@ private Path getLinePath(String line, Paint paint) { double previousWidth = 0; char[] chars = line.toCharArray(); + /* + * + * Three properties affect the space between characters and words: + * + * ‘kerning’ indicates whether the user agent should adjust inter-glyph spacing + * based on kerning tables that are included in the relevant font + * (i.e., enable auto-kerning) or instead disable auto-kerning + * and instead set inter-character spacing to a specific length (typically, zero). + * + * ‘letter-spacing’ indicates an amount of space that is to be added between text + * characters supplemental to any spacing due to the ‘kerning’ property. + * + * ‘word-spacing’ indicates the spacing behavior between words. + * + * Letter-spacing is applied after bidi reordering and is in addition to any word-spacing. + * Depending on the justification rules in effect, user agents may further increase + * or decrease the space between typographic character units in order to justify text. + * + * */ + boolean hasKerning = font.hasKey(PROP_KERNING); double kerning = hasKerning ? font.getDouble(PROP_KERNING) * mScale : DEFAULT_KERNING; boolean autoKerning = !hasKerning; + double wordSpacing = font.hasKey(PROP_WORD_SPACING) ? + font.getDouble(PROP_WORD_SPACING) * mScale + : DEFAULT_WORD_SPACING; + double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? font.getDouble(PROP_LETTER_SPACING) * mScale : DEFAULT_LETTER_SPACING; for (int index = 0; index < length; index++) { glyph = new Path(); - current = String.valueOf(chars[index]); + final char currentChar = chars[index]; + current = String.valueOf(currentChar); paint.getTextPath(current, 0, 1, 0, 0, glyph); width = paint.measureText(current) * renderMethodScaling; @@ -179,7 +206,10 @@ private Path getLinePath(String line, Paint paint) { previous = current; } - x = gc.nextX(width + kerning + letterSpacing); + boolean isWordSeparator = currentChar == ' '; + double wordSpace = isWordSeparator ? wordSpacing : 0; + + x = gc.nextX(width + kerning + wordSpace + letterSpacing); y = gc.nextY(); dx = gc.nextDeltaX(); dy = gc.nextDeltaY(); From 99995056a9d046979c664880dcd7b7d58042e20e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 07:45:58 +0300 Subject: [PATCH 111/198] Improve naming --- .../java/com/horcrux/svg/TSpanShadowNode.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 06aa8a4cb..e16e8a8f1 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -114,8 +114,8 @@ private double getTextAnchorShift(double width) { } private Path getLinePath(String line, Paint paint) { - int length = line.length(); - Path path = new Path(); + final int length = line.length(); + final Path path = new Path(); if (length == 0) { return path; @@ -128,17 +128,17 @@ private Path getLinePath(String line, Paint paint) { double offset = 0; double distance = 0; double renderMethodScaling = 1; - double textMeasure = paint.measureText(line); + final double textMeasure = paint.measureText(line); PathMeasure pm = null; if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); - double size = gc.getFontSize(); - String startOffset = textPath.getStartOffset(); + final double size = gc.getFontSize(); + final String startOffset = textPath.getStartOffset(); offset = PropHelper.fromRelative(startOffset, distance, 0, mScale, size); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" - String method = textPath.getMethod(); // method = "align | stretch" + final String method = textPath.getMethod(); // method = "align | stretch" if (STRETCH.equals(method)) { renderMethodScaling = distance / textMeasure; } @@ -153,12 +153,12 @@ private Path getLinePath(String line, Paint paint) { double dy; Path glyph; - double width; Matrix matrix; String current; + double glyphWidth; String previous = ""; - double previousWidth = 0; - char[] chars = line.toCharArray(); + double previousGlyphWidth = 0; + final char[] chars = line.toCharArray(); /* * @@ -180,15 +180,15 @@ private Path getLinePath(String line, Paint paint) { * * */ - boolean hasKerning = font.hasKey(PROP_KERNING); + final boolean hasKerning = font.hasKey(PROP_KERNING); double kerning = hasKerning ? font.getDouble(PROP_KERNING) * mScale : DEFAULT_KERNING; - boolean autoKerning = !hasKerning; + final boolean autoKerning = !hasKerning; - double wordSpacing = font.hasKey(PROP_WORD_SPACING) ? + final double wordSpacing = font.hasKey(PROP_WORD_SPACING) ? font.getDouble(PROP_WORD_SPACING) * mScale : DEFAULT_WORD_SPACING; - double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? + final double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? font.getDouble(PROP_LETTER_SPACING) * mScale : DEFAULT_LETTER_SPACING; @@ -197,19 +197,20 @@ private Path getLinePath(String line, Paint paint) { final char currentChar = chars[index]; current = String.valueOf(currentChar); paint.getTextPath(current, 0, 1, 0, 0, glyph); - width = paint.measureText(current) * renderMethodScaling; + glyphWidth = paint.measureText(current) * renderMethodScaling; if (autoKerning) { - double both = paint.measureText(previous + current) * renderMethodScaling; - kerning = both - previousWidth - width; - previousWidth = width; + double bothGlyphWidth = paint.measureText(previous + current) * renderMethodScaling; + kerning = bothGlyphWidth - previousGlyphWidth - glyphWidth; + previousGlyphWidth = glyphWidth; previous = current; } - boolean isWordSeparator = currentChar == ' '; - double wordSpace = isWordSeparator ? wordSpacing : 0; + final boolean isWordSeparator = currentChar == ' '; + final double wordSpace = isWordSeparator ? wordSpacing : 0; + final double advance = glyphWidth + kerning + wordSpace + letterSpacing; - x = gc.nextX(width + kerning + wordSpace + letterSpacing); + x = gc.nextX(advance); y = gc.nextY(); dx = gc.nextDeltaX(); dy = gc.nextDeltaY(); @@ -217,11 +218,11 @@ private Path getLinePath(String line, Paint paint) { matrix = new Matrix(); - double xSum = offset + x + dx - width; + final double glyphStart = offset + x + dx - glyphWidth; if (textPath != null) { - double halfway = width / 2; - double midpoint = xSum + halfway; + double halfGlyphWidth = glyphWidth / 2; + double midpoint = glyphStart + halfGlyphWidth; if (midpoint > distance) { break; @@ -232,11 +233,11 @@ private Path getLinePath(String line, Paint paint) { assert pm != null; pm.getMatrix((float) midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - matrix.preTranslate((float) -halfway, (float) dy); + matrix.preTranslate((float) -halfGlyphWidth, (float) dy); matrix.preScale((float) renderMethodScaling, (float) renderMethodScaling); matrix.postTranslate(0, (float) y); } else { - matrix.setTranslate((float) xSum, (float) (y + dy)); + matrix.setTranslate((float) glyphStart, (float) (y + dy)); } matrix.preRotate((float) r); From dea6d2e8f0e297b697ed6ae2bfdb169c17765ebb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 07:54:44 +0300 Subject: [PATCH 112/198] Simplify scaling logic --- .../java/com/horcrux/svg/GlyphContext.java | 22 +++++++++---------- .../java/com/horcrux/svg/TSpanShadowNode.java | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 18eed2b06..54800dbd8 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -188,15 +188,15 @@ private static void put(String key, WritableMap map, ReadableMap font, ReadableM } } - private static void putD(String key, WritableMap map, ReadableMap font, ReadableMap parent, double fontSize) { + private void putD(String key, WritableMap map, ReadableMap font, ReadableMap parent) { if (font.hasKey(key)) { String string = font.getString(key); double value = PropHelper.fromRelative( string, 0, 0, - 1, - fontSize + mScale, + mFontSize ); map.putDouble(key, value); } else if (parent.hasKey(key)) { @@ -244,13 +244,13 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { // https://www.w3.org/TR/SVG11/text.html#SpacingProperties // https://drafts.csswg.org/css-text-3/#spacing - // calculated values for units in: kerning, letter-spacing and word-spacing + // calculated values for units in: kerning, word-spacing, and, letter-spacing - putD(KERNING, map, font, parent, mFontSize); + putD(KERNING, map, font, parent); - putD(WORD_SPACING, map, font, parent, mFontSize); + putD(WORD_SPACING, map, font, parent); - putD(LETTER_SPACING, map, font, parent, mFontSize); + putD(LETTER_SPACING, map, font, parent); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { @@ -465,8 +465,8 @@ private static void incrementIndices(ArrayList indices, int topIndex) { if (nextIndex < mDXs.length) { mDXIndex = nextIndex; String string = mDXs[nextIndex]; - double val = PropHelper.fromRelative(string, mWidth, 0, 1, mFontSize); - mDX += val * mScale; + double val = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize); + mDX += val; } return mDX; @@ -479,8 +479,8 @@ private static void incrementIndices(ArrayList indices, int topIndex) { if (nextIndex < mDYs.length) { mDYIndex = nextIndex; String string = mDYs[nextIndex]; - double val = PropHelper.fromRelative(string, mHeight, 0, 1, mFontSize); - mDY += val * mScale; + double val = PropHelper.fromRelative(string, mHeight, 0, mScale, mFontSize); + mDY += val; } return mDY; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index e16e8a8f1..2f5863fd5 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -181,15 +181,15 @@ private Path getLinePath(String line, Paint paint) { * */ final boolean hasKerning = font.hasKey(PROP_KERNING); - double kerning = hasKerning ? font.getDouble(PROP_KERNING) * mScale : DEFAULT_KERNING; + double kerning = hasKerning ? font.getDouble(PROP_KERNING) : DEFAULT_KERNING; final boolean autoKerning = !hasKerning; final double wordSpacing = font.hasKey(PROP_WORD_SPACING) ? - font.getDouble(PROP_WORD_SPACING) * mScale + font.getDouble(PROP_WORD_SPACING) : DEFAULT_WORD_SPACING; final double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? - font.getDouble(PROP_LETTER_SPACING) * mScale + font.getDouble(PROP_LETTER_SPACING) : DEFAULT_LETTER_SPACING; for (int index = 0; index < length; index++) { From dff02c3dbbe1d936fd243acdaf4f2fea0ced37b3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 08:30:28 +0300 Subject: [PATCH 113/198] Implement wordSpacing in extractFont --- lib/attributes.js | 1 + lib/extract/extractText.js | 1 + lib/props.js | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/attributes.js b/lib/attributes.js index 27c8dbedb..0872e7a29 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -25,6 +25,7 @@ function fontDiffer(a, b) { a.fontStyle !== b.fontStyle || a.fontWeight !== b.fontWeight || a.kerning !== b.kerning || + a.wordSpacing !== b.wordSpacing || a.letterSpacing !== b.letterSpacing; } diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 4530cc841..bf6235217 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -60,6 +60,7 @@ export function extractFont(props) { let ownedFont = { fontFamily: extractSingleFontFamily(props.fontFamily), letterSpacing: props.letterSpacing, + wordSpacing: props.wordSpacing, fontWeight: props.fontWeight, fontStyle: props.fontStyle, fontSize: props.fontSize ? '' + props.fontSize : null, diff --git a/lib/props.js b/lib/props.js index 6e8d6bdb2..1140650a3 100644 --- a/lib/props.js +++ b/lib/props.js @@ -54,6 +54,7 @@ const fontProps = { fontWeight: numberProp, fontStyle: PropTypes.string, letterSpacing: PropTypes.string, + wordSpacing: PropTypes.string, kerning: PropTypes.string, font: PropTypes.object }; From be811a4b4e3e7a49f087958db7419f1e9e668ef7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 10:12:32 +0300 Subject: [PATCH 114/198] Fix missing newline in docs --- android/src/main/java/com/horcrux/svg/PropHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 6448bdbf7..1f2dafdd5 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -89,7 +89,8 @@ static int toMatrixData(ReadableArray value, float[] sRawMatrix, float mScale) { * @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 + * @param fontSize current font size + * @return value in the current user coordinate system */ static double fromRelative(String length, double relative, double offset, double scale, double fontSize) { /* From 2b3f251b8bf5adf4a58b79e4fa13571aacfcfa1c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 16:39:18 +0300 Subject: [PATCH 115/198] Docfix --- .../java/com/horcrux/svg/ImageShadowNode.java | 2 +- .../main/java/com/horcrux/svg/PropHelper.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index 0954c0ca2..4e29c5959 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -209,9 +209,9 @@ private void doRender(Canvas canvas, Paint paint, Bitmap bitmap, float opacity) renderRect = new RectF(0, 0, (int)rectWidth, (int)(rectWidth / mImageRatio)); } - RectF vbRect = new RectF(0, 0, renderRect.width() / mScale, renderRect.height() / mScale); float canvasLeft = getCanvasLeft(); float canvasTop = getCanvasTop(); + RectF vbRect = new RectF(0, 0, renderRect.width() / mScale, renderRect.height() / mScale); RectF eRect = new RectF(canvasLeft, canvasTop, rectWidth / mScale + canvasLeft, rectHeight / mScale + canvasTop); Matrix transform = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 1f2dafdd5..4482ea046 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -96,15 +96,15 @@ static double fromRelative(String length, double relative, double offset, double /* 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 + 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 ] From fc61c9dad2f29e16cf7e09320474ec000f1dba87 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 20:11:01 +0300 Subject: [PATCH 116/198] Simplify textAnchor and textDecoration handling --- .../java/com/horcrux/svg/GlyphContext.java | 32 +++--- .../java/com/horcrux/svg/TSpanShadowNode.java | 65 ++++++----- .../java/com/horcrux/svg/TextShadowNode.java | 86 +++------------ elements/TSpan.js | 1 - elements/Text.js | 1 - lib/extract/extractText.js | 101 +++++++++--------- lib/props.js | 2 + 7 files changed, 124 insertions(+), 164 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 54800dbd8..5d4bdb81a 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -18,18 +18,20 @@ import javax.annotation.Nullable; +import static com.horcrux.svg.TextShadowNode.KERNING; +import static com.horcrux.svg.TextShadowNode.TEXT_ANCHOR; +import static com.horcrux.svg.TextShadowNode.WORD_SPACING; +import static com.horcrux.svg.TextShadowNode.LETTER_SPACING; +import static com.horcrux.svg.TextShadowNode.TEXT_DECORATION; +import static com.facebook.react.uimanager.ViewProps.FONT_SIZE; +import static com.facebook.react.uimanager.ViewProps.FONT_STYLE; +import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY; +import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT; + // https://www.w3.org/TR/SVG/text.html#TSpanElement class GlyphContext { static final double DEFAULT_FONT_SIZE = 12d; - private static final String KERNING = "kerning"; - private static final String FONT_SIZE = "fontSize"; - private static final String FONT_STYLE = "fontStyle"; - private static final String FONT_WEIGHT = "fontWeight"; - private static final String FONT_FAMILY = "fontFamily"; - private static final String WORD_SPACING = "wordSpacing"; - private static final String LETTER_SPACING = "letterSpacing"; - // Empty font context map private static final WritableMap DEFAULT_MAP = Arguments.createMap(); @@ -236,20 +238,18 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { map.putDouble(FONT_SIZE, mFontSize); + put(FONT_STYLE, map, font, parent); put(FONT_FAMILY, map, font, parent); - put(FONT_WEIGHT, map, font, parent); - put(FONT_STYLE, map, font, parent); + put(TEXT_ANCHOR, map, font, parent); + put(TEXT_DECORATION, map, font, parent); // https://www.w3.org/TR/SVG11/text.html#SpacingProperties // https://drafts.csswg.org/css-text-3/#spacing - // calculated values for units in: kerning, word-spacing, and, letter-spacing - + // calculated values for units in: kerning, word-spacing, and, letter-spacing. putD(KERNING, map, font, parent); - putD(WORD_SPACING, map, font, parent); - putD(LETTER_SPACING, map, font, parent); } @@ -428,7 +428,7 @@ private static void incrementIndices(ArrayList indices, int topIndex) { return mFontSize; } - double nextX(double glyphWidth) { + double nextX(double advance) { incrementIndices(mXIndices, mXsIndex); int nextIndex = mXIndex + 1; @@ -439,7 +439,7 @@ private static void incrementIndices(ArrayList indices, int topIndex) { mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize); } - mX += glyphWidth; + mX += advance; return mX; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 2f5863fd5..c2121b09f 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -28,6 +28,11 @@ import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG; import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG; +import static com.facebook.react.uimanager.ViewProps.FONT_SIZE; +import static com.facebook.react.uimanager.ViewProps.FONT_STYLE; +import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT; +import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY; + /** * Shadow node for virtual TSpan view */ @@ -44,14 +49,6 @@ class TSpanShadowNode extends TextShadowNode { private static final double DEFAULT_WORD_SPACING = 0d; private static final double DEFAULT_LETTER_SPACING = 0d; - private static final String PROP_KERNING = "kerning"; - 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 String PROP_FONT_FAMILY = "fontFamily"; - private static final String PROP_WORD_SPACING = "wordSpacing"; - private static final String PROP_LETTER_SPACING = "letterSpacing"; - private Path mCache; private @Nullable String mContent; private TextPathShadowNode textPath; @@ -98,10 +95,10 @@ protected Path getPath(Canvas canvas, Paint paint) { return mCache; } - private double getTextAnchorShift(double width) { + private double getTextAnchorShift(double width, String textAnchor) { double x = 0; - switch (getComputedTextAnchor()) { + switch (textAnchor) { case TEXT_ANCHOR_MIDDLE: x = -width / 2; break; @@ -144,7 +141,9 @@ private Path getLinePath(String line, Paint paint) { } } - offset += getTextAnchorShift(textMeasure); + if (font.hasKey(TEXT_ANCHOR)) { + offset += getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR)); + } double x; double y; @@ -180,16 +179,16 @@ private Path getLinePath(String line, Paint paint) { * * */ - final boolean hasKerning = font.hasKey(PROP_KERNING); - double kerning = hasKerning ? font.getDouble(PROP_KERNING) : DEFAULT_KERNING; + final boolean hasKerning = font.hasKey(KERNING); + double kerning = hasKerning ? font.getDouble(KERNING) : DEFAULT_KERNING; final boolean autoKerning = !hasKerning; - final double wordSpacing = font.hasKey(PROP_WORD_SPACING) ? - font.getDouble(PROP_WORD_SPACING) + final double wordSpacing = font.hasKey(WORD_SPACING) ? + font.getDouble(WORD_SPACING) : DEFAULT_WORD_SPACING; - final double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? - font.getDouble(PROP_LETTER_SPACING) + final double letterSpacing = font.hasKey(LETTER_SPACING) ? + font.getDouble(LETTER_SPACING) : DEFAULT_LETTER_SPACING; for (int index = 0; index < length; index++) { @@ -251,19 +250,29 @@ private Path getLinePath(String line, Paint paint) { private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { AssetManager assetManager = getThemedContext().getResources().getAssets(); - double fontSize = font.getDouble(PROP_FONT_SIZE) * mScale; + double fontSize = font.getDouble(FONT_SIZE) * mScale; - boolean isBold = font.hasKey(PROP_FONT_WEIGHT) && - BOLD.equals(font.getString(PROP_FONT_WEIGHT)); + boolean isBold = font.hasKey(FONT_WEIGHT) && + BOLD.equals(font.getString(FONT_WEIGHT)); - boolean isItalic = font.hasKey(PROP_FONT_STYLE) && - ITALIC.equals(font.getString(PROP_FONT_STYLE)); + boolean isItalic = font.hasKey(FONT_STYLE) && + ITALIC.equals(font.getString(FONT_STYLE)); - int decoration = getTextDecoration(); + boolean underlineText = false; + boolean strikeThruText = false; - boolean underlineText = decoration == TEXT_DECORATION_UNDERLINE; + if (font.hasKey(TEXT_DECORATION)) { + String decoration = font.getString(TEXT_DECORATION); + switch (decoration) { + case TEXT_DECORATION_UNDERLINE: + underlineText = true; + break; - boolean strikeThruText = decoration == TEXT_DECORATION_LINE_THROUGH; + case TEXT_DECORATION_LINE_THROUGH: + strikeThruText = true; + break; + } + } int fontStyle; if (isBold && isItalic) { @@ -278,15 +287,15 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { Typeface typeface = null; try { - String path = FONTS + font.getString(PROP_FONT_FAMILY) + OTF; + String path = FONTS + font.getString(FONT_FAMILY) + OTF; typeface = Typeface.createFromAsset(assetManager, path); } catch (Exception ignored) { try { - String path = FONTS + font.getString(PROP_FONT_FAMILY) + TTF; + String path = FONTS + font.getString(FONT_FAMILY) + TTF; typeface = Typeface.createFromAsset(assetManager, path); } catch (Exception ignored2) { try { - typeface = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle); + typeface = Typeface.create(font.getString(FONT_FAMILY), fontStyle); } catch (Exception ignored3) { } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 420bf7c15..4ee00ba84 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -15,7 +15,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; import javax.annotation.Nullable; @@ -25,38 +24,31 @@ */ class TextShadowNode extends GroupShadowNode { + // static final String INHERIT = "inherit"; - private static final int TEXT_ANCHOR_AUTO = 0; - // static final int TEXT_ANCHOR_START = 1; - static final int TEXT_ANCHOR_MIDDLE = 2; - static final int TEXT_ANCHOR_END = 3; + // static final String TEXT_ANCHOR_AUTO = "auto"; + // static final String TEXT_ANCHOR_START = "start"; + static final String TEXT_ANCHOR_MIDDLE = "middle"; + static final String TEXT_ANCHOR_END = "end"; - private static final int TEXT_DECORATION_NONE = 0; - static final int TEXT_DECORATION_UNDERLINE = 1; - // static final int TEXT_DECORATION_OVERLINE = 2; - static final int TEXT_DECORATION_LINE_THROUGH = 3; - // static final int TEXT_DECORATION_BLINK = 4; + // static final String TEXT_DECORATION_NONE = "none"; + static final String TEXT_DECORATION_UNDERLINE = "underline"; + // static final String TEXT_DECORATION_OVERLINE = "overline"; + static final String TEXT_DECORATION_LINE_THROUGH = "line-through"; + // static final String TEXT_DECORATION_BLINK = "blink"; + + static final String KERNING = "kerning"; + static final String TEXT_ANCHOR = "textAnchor"; + static final String WORD_SPACING = "wordSpacing"; + static final String LETTER_SPACING = "letterSpacing"; + static final String TEXT_DECORATION = "textDecoration"; - private int mTextAnchor = TEXT_ANCHOR_AUTO; - private int mTextDecoration = TEXT_DECORATION_NONE; private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; private @Nullable ReadableArray mRotate; private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaY; - @ReactProp(name = "textAnchor") - public void setTextAnchor(int textAnchor) { - mTextAnchor = textAnchor; - markUpdated(); - } - - @ReactProp(name = "textDecoration") - public void setTextDecoration(int textDecoration) { - mTextDecoration = textDecoration; - markUpdated(); - } - @ReactProp(name = "rotate") public void setRotate(@Nullable ReadableArray rotate) { mRotate = rotate; @@ -112,52 +104,6 @@ protected Path getPath(Canvas canvas, Paint paint) { return groupPath; } - private int getTextAnchor() { - return mTextAnchor; - } - - int getComputedTextAnchor() { - int anchor = mTextAnchor; - if (anchor != TEXT_ANCHOR_AUTO) { - return anchor; - } - ReactShadowNode shadowNode = this.getParent(); - - while (shadowNode instanceof GroupShadowNode) { - if (shadowNode instanceof TextShadowNode) { - anchor = ((TextShadowNode) shadowNode).getTextAnchor(); - if (anchor != TEXT_ANCHOR_AUTO) { - break; - } - } - - shadowNode = shadowNode.getParent(); - } - - return anchor; - } - - int getTextDecoration() { - int decoration = mTextDecoration; - if (decoration != TEXT_DECORATION_NONE) { - return decoration; - } - ReactShadowNode shadowNode = this.getParent(); - - while (shadowNode instanceof GroupShadowNode) { - if (shadowNode instanceof TextShadowNode) { - decoration = ((TextShadowNode) shadowNode).getTextDecoration(); - if (decoration != TEXT_DECORATION_NONE) { - break; - } - } - - shadowNode = shadowNode.getParent(); - } - - return decoration; - } - void releaseCachedPath() { traverseChildren(new NodeRunnable() { public void run(VirtualNode node) { diff --git a/elements/TSpan.js b/elements/TSpan.js index 4f97de410..c272ea731 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -16,7 +16,6 @@ export default class extends Shape { ...fontProps, dx: numberProp, dy: numberProp, - textAnchor: PropTypes.oneOf(['start', 'middle', 'end']) }; static childContextTypes = { diff --git a/elements/Text.js b/elements/Text.js index 60f86dd11..ee1e0c0d9 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -15,7 +15,6 @@ export default class extends Shape { ...fontProps, dx: numberProp, dy: numberProp, - textAnchor: PropTypes.oneOf(['start', 'middle', 'end']) }; static childContextTypes = { diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index bf6235217..b9dfe5ae1 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -8,21 +8,7 @@ const fontFamilySuffix = /[\s"']*$/; const spaceReg = /\s+/; const commaReg = /,/g; -const anchors = { - auto: 0, - start: 1, - middle: 2, - end: 3 -}; - -const decorations = { - none: 0, - underline: 1, - overline: 2, - 'line-through': 3, - blink: 4 -}; -let cachedFontObjectsFromString = {}; +const cachedFontObjectsFromString = {}; function extractSingleFontFamily(fontFamilyString) { // SVG on the web allows for multiple font-families to be specified. @@ -37,40 +23,59 @@ function parseFontString(font) { if (cachedFontObjectsFromString.hasOwnProperty(font)) { return cachedFontObjectsFromString[font]; } - let match = fontRegExp.exec(font); + const match = fontRegExp.exec(font); if (!match) { return null; } - let fontFamily = extractSingleFontFamily(match[3]); - let fontSize = match[2] || "12"; - let isBold = /bold/.exec(match[1]); - let isItalic = /italic/.exec(match[1]); + const fontFamily = extractSingleFontFamily(match[3]); + const fontSize = match[2] || "12"; + const isBold = /bold/.exec(match[1]); + const isItalic = /italic/.exec(match[1]); + const fontWeight = isBold ? 'bold' : 'normal'; + const fontStyle = isItalic ? 'italic' : 'normal'; cachedFontObjectsFromString[font] = { fontSize, fontFamily, - fontWeight: isBold ? 'bold' : 'normal', - fontStyle: isItalic ? 'italic' : 'normal' + fontWeight, + fontStyle, }; return cachedFontObjectsFromString[font]; } export function extractFont(props) { - let font = props.font; + const { + letterSpacing, + wordSpacing, + fontWeight, + fontStyle, + kerning, + textAnchor, + textDecoration, + } = props; + let { + font, + fontSize, + fontFamily, + } = props; - let ownedFont = { - fontFamily: extractSingleFontFamily(props.fontFamily), - letterSpacing: props.letterSpacing, - wordSpacing: props.wordSpacing, - fontWeight: props.fontWeight, - fontStyle: props.fontStyle, - fontSize: props.fontSize ? '' + props.fontSize : null, - kerning: props.kerning, - }; + fontFamily = extractSingleFontFamily(fontFamily); + fontSize = fontSize ? '' + fontSize : null; + + const ownedFont = _.pickBy({ + fontFamily, + letterSpacing, + wordSpacing, + fontWeight, + fontStyle, + fontSize, + kerning, + textAnchor, + textDecoration, + }, prop => !_.isNil(prop)); - if (typeof props.font === 'string') { - font = parseFontString(props.font); + if (typeof font === 'string') { + font = parseFontString(font); } - ownedFont = _.pickBy(ownedFont, prop => !_.isNil(prop)); return _.defaults(ownedFont, font); } @@ -88,28 +93,27 @@ function parseSVGLengthList(delta) { } export default function(props, container) { - let { + const { x, y, dx, dy, - rotate, method, spacing, - textAnchor, - startOffset, - textDecoration + } = props; + let { + rotate, + children, + startOffset } = props; - let positionX = parseSVGLengthList(x); - let positionY = parseSVGLengthList(y); + const positionX = parseSVGLengthList(x); + const positionY = parseSVGLengthList(y); const deltaX = parseSVGLengthList(dx); const deltaY = parseSVGLengthList(dy); rotate = parseSVGLengthList(rotate); - let { children } = props; let content = null; - if (typeof children === 'string' || typeof children === 'number') { const childrenString = children.toString(); if (container) { @@ -128,10 +132,11 @@ export default function(props, container) { }); } + const font = extractFont(props); + startOffset = (startOffset || 0).toString(); + return { - textDecoration: decorations[textDecoration] || 0, - textAnchor: anchors[textAnchor] || 0, - font: extractFont(props), + font, children, content, positionX, @@ -141,6 +146,6 @@ export default function(props, container) { deltaY, method, spacing, - startOffset: (startOffset || 0).toString(), + startOffset, }; } diff --git a/lib/props.js b/lib/props.js index 1140650a3..6398e2a0b 100644 --- a/lib/props.js +++ b/lib/props.js @@ -53,6 +53,8 @@ const fontProps = { fontSize: numberProp, fontWeight: numberProp, fontStyle: PropTypes.string, + textAnchor: PropTypes.string, + textDecoration: PropTypes.string, letterSpacing: PropTypes.string, wordSpacing: PropTypes.string, kerning: PropTypes.string, From 80b9ff9d57d58b6cb05a24a37adf8717d972e1db Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 23 Jul 2017 22:55:39 +0300 Subject: [PATCH 117/198] Fix font PropTypes --- .../java/com/horcrux/svg/TSpanShadowNode.java | 14 +-- elements/TSpan.js | 4 +- elements/Text.js | 4 +- elements/TextPath.js | 2 +- lib/extract/extractText.js | 24 +++--- lib/props.js | 86 ++++++++++++++++--- 6 files changed, 103 insertions(+), 31 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index c2121b09f..cc4250d2f 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -122,10 +122,11 @@ private Path getLinePath(String line, Paint paint) { ReadableMap font = gc.getFont(); applyTextPropertiesToPaint(paint, font); - double offset = 0; double distance = 0; double renderMethodScaling = 1; final double textMeasure = paint.measureText(line); + double offset = font.hasKey(TEXT_ANCHOR) ? + getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR)) : 0; PathMeasure pm = null; if (textPath != null) { @@ -133,7 +134,7 @@ private Path getLinePath(String line, Paint paint) { distance = pm.getLength(); final double size = gc.getFontSize(); final String startOffset = textPath.getStartOffset(); - offset = PropHelper.fromRelative(startOffset, distance, 0, mScale, size); + offset += PropHelper.fromRelative(startOffset, distance, 0, mScale, size); // String spacing = textPath.getSpacing(); // spacing = "auto | exact" final String method = textPath.getMethod(); // method = "align | stretch" if (STRETCH.equals(method)) { @@ -141,10 +142,6 @@ private Path getLinePath(String line, Paint paint) { } } - if (font.hasKey(TEXT_ANCHOR)) { - offset += getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR)); - } - double x; double y; double r; @@ -231,6 +228,11 @@ private Path getLinePath(String line, Paint paint) { assert pm != null; pm.getMatrix((float) midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); + // TODO In the calculation above, if either the startpoint-on-the-path + // or the endpoint-on-the-path is off the end of the path, + // then extend the path beyond its end points with a straight line + // that is parallel to the tangent at the path at its end point + // so that the midpoint-on-the-path can still be calculated. matrix.preTranslate((float) -halfGlyphWidth, (float) dy); matrix.preScale((float) renderMethodScaling, (float) renderMethodScaling); diff --git a/elements/TSpan.js b/elements/TSpan.js index c272ea731..ddb1bc2d0 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -14,8 +14,8 @@ export default class extends Shape { static propTypes = { ...pathProps, ...fontProps, - dx: numberProp, - dy: numberProp, + dx: PropTypes.string, + dy: PropTypes.string, }; static childContextTypes = { diff --git a/elements/Text.js b/elements/Text.js index ee1e0c0d9..cbcb3a7f0 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -13,8 +13,8 @@ export default class extends Shape { static propTypes = { ...pathProps, ...fontProps, - dx: numberProp, - dy: numberProp, + dx: PropTypes.string, + dy: PropTypes.string, }; static childContextTypes = { diff --git a/elements/TextPath.js b/elements/TextPath.js index 3e1769fb6..78c1602f9 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -19,7 +19,7 @@ export default class extends Shape { href: PropTypes.string.isRequired, method: PropTypes.oneOf(['align', 'stretch']), spacing: PropTypes.oneOf(['auto', 'exact']), - startOffset: numberProp + startOffset: PropTypes.string }; render() { diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index b9dfe5ae1..214f297c2 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -44,33 +44,37 @@ function parseFontString(font) { export function extractFont(props) { const { - letterSpacing, - wordSpacing, - fontWeight, fontStyle, - kerning, + fontVariant, + fontWeight, + fontStretch, textAnchor, textDecoration, + letterSpacing, + wordSpacing, + kerning, } = props; let { - font, fontSize, fontFamily, + font, } = props; fontFamily = extractSingleFontFamily(fontFamily); fontSize = fontSize ? '' + fontSize : null; const ownedFont = _.pickBy({ - fontFamily, - letterSpacing, - wordSpacing, - fontWeight, fontStyle, + fontVariant, + fontWeight, + fontStretch, fontSize, - kerning, + fontFamily, textAnchor, textDecoration, + letterSpacing, + wordSpacing, + kerning, }, prop => !_.isNil(prop)); if (typeof font === 'string') { diff --git a/lib/props.js b/lib/props.js index 6398e2a0b..7523aeec8 100644 --- a/lib/props.js +++ b/lib/props.js @@ -48,17 +48,83 @@ const strokeProps = { strokeMiterlimit: numberProp }; +// normal | italic | oblique | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style +const fontStyle = PropTypes.oneOf(['normal', 'italic', 'oblique']); + +// normal | small-caps | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant +const fontVariant = PropTypes.oneOf(['normal', 'small-caps']); + +// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight +const fontWeight = PropTypes.oneOf(['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '700', '800', '900']); + +// normal | wider | narrower | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch +const fontStretch = PropTypes.oneOf(['normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded']); + +// | | | | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size +const fontSize = PropTypes.string; + +// [[ | ],]* [ | ] | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family +const fontFamily = PropTypes.string; + +/* + font syntax [ [ <'font-style'> || || <'font-weight'> || <'font-stretch'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar + where = [ normal | small-caps ] + + Shorthand property for setting ‘font-style’, ‘font-variant’, + ‘font-weight’, ‘font-size’, ‘line-height’ and ‘font-family’. + + The ‘line-height’ property has no effect on text layout in SVG. + + Note: for the purposes of processing the ‘font’ property in SVG, + 'line-height' is assumed to be equal the value for property ‘font-size’ + + https://www.w3.org/TR/SVG11/text.html#FontProperty + https://developer.mozilla.org/en-US/docs/Web/CSS/font + https://drafts.csswg.org/css-fonts-3/#font-prop + https://www.w3.org/TR/CSS2/fonts.html#font-shorthand + https://www.w3.org/TR/CSS1/#font +*/ +const font = PropTypes.object; + +// start | middle | end | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor +const textAnchor = PropTypes.oneOf(['start', 'middle', 'end', 'inherit']); + +// none | underline | overline | line-through | blink | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration +const textDecoration = PropTypes.oneOf(['none', 'underline', 'overline', 'line-through', 'blink']); + +// normal | | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing +const letterSpacing = PropTypes.string; + +// normal | | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/word-spacing +const wordSpacing = PropTypes.string; + +// auto | | inherit +// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning +const kerning = PropTypes.string; + const fontProps = { - fontFamily: PropTypes.string, - fontSize: numberProp, - fontWeight: numberProp, - fontStyle: PropTypes.string, - textAnchor: PropTypes.string, - textDecoration: PropTypes.string, - letterSpacing: PropTypes.string, - wordSpacing: PropTypes.string, - kerning: PropTypes.string, - font: PropTypes.object + fontStyle, + fontVariant, + fontWeight, + fontStretch, + fontSize, + fontFamily, + textAnchor, + textDecoration, + letterSpacing, + wordSpacing, + kerning, + font }; const transformProps = { From 1a9a051ee99d5e4039e31c169ce045369f895ea1 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 00:08:28 +0300 Subject: [PATCH 118/198] Fix linting, warnings, add noinspection suppression comments. --- README.md | 1 + elements/Image.js | 1 + elements/Polygon.js | 1 + elements/Polyline.js | 1 + elements/Shape.js | 1 + elements/Svg.js | 5 +++- elements/TSpan.js | 5 +++- elements/Text.js | 5 +++- elements/TextPath.js | 2 +- index.js | 1 + lib/Matrix2D.js | 43 +++++++++++++++++++++------------ lib/SvgTouchableMixin.js | 1 + lib/attributes.js | 5 ++-- lib/extract/extractBrush.js | 3 +-- lib/extract/extractFill.js | 3 +-- lib/extract/extractGradient.js | 1 + lib/extract/extractProps.js | 2 +- lib/extract/extractStroke.js | 2 +- lib/extract/extractText.js | 3 ++- lib/extract/extractTransform.js | 25 ++++++------------- package.json | 4 ++- 21 files changed, 66 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 5123e9681..a3089c1f6 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Here's a simple example. To render output like this: Use the following code: ```javascript +import 'react'; import Svg,{ Circle, Ellipse, diff --git a/elements/Image.js b/elements/Image.js index 4248ed0e2..2b3c4b5d9 100644 --- a/elements/Image.js +++ b/elements/Image.js @@ -5,6 +5,7 @@ import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shi import {ImageAttributes} from '../lib/attributes'; import {numberProp, touchableProps, responderProps} from '../lib/props'; import Shape from './Shape'; +//noinspection JSUnresolvedVariable import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import {meetOrSliceTypes, alignEnum} from '../lib/extract/extractViewBox'; import extractProps from '../lib/extract/extractProps'; diff --git a/elements/Polygon.js b/elements/Polygon.js index d0e18f1df..23e370255 100644 --- a/elements/Polygon.js +++ b/elements/Polygon.js @@ -16,6 +16,7 @@ export default class extends Component{ }; setNativeProps = (...args) => { + //noinspection JSUnresolvedFunction this.root.getNativeElement().setNativeProps(...args); }; diff --git a/elements/Polyline.js b/elements/Polyline.js index 5520c21df..34aacc6dd 100644 --- a/elements/Polyline.js +++ b/elements/Polyline.js @@ -16,6 +16,7 @@ export default class extends Component{ }; setNativeProps = (...args) => { + //noinspection JSUnresolvedFunction this.root.getNativeElement().setNativeProps(...args); }; diff --git a/elements/Shape.js b/elements/Shape.js index f77e6d913..fbf257eb1 100644 --- a/elements/Shape.js +++ b/elements/Shape.js @@ -8,6 +8,7 @@ class Shape extends Component { _.forEach(SvgTouchableMixin, (method, key) => { this[key] = method.bind(this); }); + //noinspection JSUnusedGlobalSymbols this.state = this.touchableGetInitialState(); } } diff --git a/elements/Svg.js b/elements/Svg.js index f5e081450..377dadd83 100644 --- a/elements/Svg.js +++ b/elements/Svg.js @@ -1,3 +1,4 @@ +//noinspection JSUnresolvedVariable import React, { Component } from 'react'; @@ -12,6 +13,7 @@ import { import extractViewBox from '../lib/extract/extractViewBox'; import {ViewBoxAttributes} from '../lib/attributes'; +/** @namespace NativeModules.RNSVGSvgViewManager */ const RNSVGSvgViewManager = NativeModules.RNSVGSvgViewManager; // Svg - Root node of all Svg elements @@ -43,6 +45,7 @@ class Svg extends Component{ super(...arguments); id++; this.id = id; + //noinspection JSUnusedGlobalSymbols this.onDataURLCallbacks = []; } measureInWindow = (...args) => { @@ -61,7 +64,7 @@ class Svg extends Component{ this.root.setNativeProps(...args); }; - toDataURL = (callback: Function) => { + toDataURL = (callback) => { callback && RNSVGSvgViewManager.toDataURL(findNodeHandle(this.root), callback); }; diff --git a/elements/TSpan.js b/elements/TSpan.js index ddb1bc2d0..540291f6c 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shims/createReactNativeComponentClass'; import extractText from '../lib/extract/extractText'; -import {numberProp, pathProps, fontProps} from '../lib/props'; +import {pathProps, fontProps} from '../lib/props'; import {TSpanAttibutes} from '../lib/attributes'; import extractProps from '../lib/extract/extractProps'; import Shape from './Shape'; @@ -18,16 +18,19 @@ export default class extends Shape { dy: PropTypes.string, }; + //noinspection JSUnusedGlobalSymbols static childContextTypes = { isInAParentText: PropTypes.bool }; + //noinspection JSUnusedGlobalSymbols getChildContext() { return { isInAParentText: true }; } + //noinspection JSUnusedGlobalSymbols getContextTypes() { return { isInAParentText: PropTypes.bool diff --git a/elements/Text.js b/elements/Text.js index cbcb3a7f0..d8d68b290 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shims/createReactNativeComponentClass'; import extractText from '../lib/extract/extractText'; -import {numberProp, pathProps, fontProps} from '../lib/props'; +import {pathProps, fontProps} from '../lib/props'; import {TextAttributes} from '../lib/attributes'; import extractProps from '../lib/extract/extractProps'; import Shape from './Shape'; @@ -17,16 +17,19 @@ export default class extends Shape { dy: PropTypes.string, }; + //noinspection JSUnusedGlobalSymbols static childContextTypes = { isInAParentText: PropTypes.bool }; + //noinspection JSUnusedGlobalSymbols getChildContext() { return { isInAParentText: true }; } + //noinspection JSUnusedGlobalSymbols getContextTypes() { return { isInAParentText: PropTypes.bool diff --git a/elements/TextPath.js b/elements/TextPath.js index 78c1602f9..c715f11e6 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -4,7 +4,7 @@ import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shi import {TextPathAttributes} from '../lib/attributes'; import extractText from '../lib/extract/extractText'; import Shape from './Shape'; -import {pathProps, fontProps, numberProp} from '../lib/props'; +import {pathProps, fontProps} from '../lib/props'; import extractProps from '../lib/extract/extractProps'; import TSpan from './TSpan'; diff --git a/index.js b/index.js index a72b59c6c..576254829 100644 --- a/index.js +++ b/index.js @@ -42,4 +42,5 @@ export { ClipPath }; +//noinspection JSUnusedGlobalSymbols export default Svg; diff --git a/lib/Matrix2D.js b/lib/Matrix2D.js index ae634e531..3a880017d 100644 --- a/lib/Matrix2D.js +++ b/lib/Matrix2D.js @@ -79,13 +79,13 @@ export default class Matrix2D { * @return {Matrix2D} This instance. Useful for chaining method calls. */ setTransform = function(a, b, c, d, tx, ty) { - /*eslint eqeqeq:0*/ - this.a = a == null ? 1 : a; + this.a = a === null || a === undefined ? 1 : a; this.b = b || 0; this.c = c || 0; - this.d = d == null ? 1 : d; + this.d = d === null || d === undefined ? 1 : d; this.tx = tx || 0; this.ty = ty || 0; + //noinspection JSValidateTypes return this; }; @@ -97,6 +97,7 @@ export default class Matrix2D { reset = function() { this.a = this.d = 1; this.b = this.c = this.tx = this.ty = 0; + //noinspection JSValidateTypes return this; }; @@ -109,6 +110,7 @@ export default class Matrix2D { return [this.a, this.b, this.c, this.d, this.tx, this.ty]; }; + //noinspection JSUnusedGlobalSymbols /** * Copies all properties from the specified matrix to this matrix. * @method copy @@ -116,9 +118,11 @@ export default class Matrix2D { * @return {Matrix2D} This matrix. Useful for chaining method calls. */ copy = function(matrix) { + //noinspection JSUnresolvedVariable return this.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; + //noinspection JSUnusedGlobalSymbols /** * Clones current instance and returning a new matrix. * @method clone @@ -142,9 +146,9 @@ export default class Matrix2D { * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ prepend = function(a, b, c, d, tx, ty) { - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; + const a1 = this.a; + const c1 = this.c; + const tx1 = this.tx; this.a = a * a1 + c * this.b; this.b = b * a1 + d * this.b; @@ -152,6 +156,7 @@ export default class Matrix2D { this.d = b * c1 + d * this.d; this.tx = a * tx1 + c * this.ty + tx; this.ty = b * tx1 + d * this.ty + ty; + //noinspection JSValidateTypes return this; }; @@ -168,10 +173,10 @@ export default class Matrix2D { * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ append = function(a, b, c, d, tx, ty) { - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; + const a1 = this.a; + const b1 = this.b; + const c1 = this.c; + const d1 = this.d; if (a !== 1 || b !== 0 || c !== 0 || d !== 1) { this.a = a1 * a + c1 * b; this.b = b1 * a + d1 * b; @@ -180,6 +185,7 @@ export default class Matrix2D { } this.tx = a1 * tx + c1 * ty + this.tx; this.ty = b1 * tx + d1 * ty + this.ty; + //noinspection JSValidateTypes return this; }; @@ -202,10 +208,11 @@ export default class Matrix2D { * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ appendTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { + let cos, sin; if (rotation % 360) { - var r = rotation * DEG_TO_RAD; - var cos = Math.cos(r); - var sin = Math.sin(r); + const r = rotation * DEG_TO_RAD; + cos = Math.cos(r); + sin = Math.sin(r); } else { cos = 1; sin = 0; @@ -226,9 +233,11 @@ export default class Matrix2D { this.tx -= regX * this.a + regY * this.c; this.ty -= regX * this.b + regY * this.d; } + //noinspection JSValidateTypes return this; }; + //noinspection JSUnusedGlobalSymbols /** * Generates matrix properties from the specified display object transform properties, and prepends them to this matrix. * For example, you could calculate the combined transformation for a child object using: @@ -255,10 +264,11 @@ export default class Matrix2D { * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { + let cos, sin; if (rotation % 360) { - var r = rotation * DEG_TO_RAD; - var cos = Math.cos(r); - var sin = Math.sin(r); + const r = rotation * DEG_TO_RAD; + cos = Math.cos(r); + sin = Math.sin(r); } else { cos = 1; sin = 0; @@ -277,6 +287,7 @@ export default class Matrix2D { } else { this.prepend(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, x, y); } + //noinspection JSValidateTypes return this; }; } diff --git a/lib/SvgTouchableMixin.js b/lib/SvgTouchableMixin.js index 790cf0b5a..0fd3c2eb5 100644 --- a/lib/SvgTouchableMixin.js +++ b/lib/SvgTouchableMixin.js @@ -1,6 +1,7 @@ import Touchable from 'react-native/Libraries/Components/Touchable/Touchable'; const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; +//noinspection JSUnusedGlobalSymbols export default { ...Touchable.Mixin, diff --git a/lib/attributes.js b/lib/attributes.js index 0872e7a29..eec6c0a0a 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -1,13 +1,12 @@ function arrayDiffer(a, b) { - /*eslint eqeqeq:0*/ - if (a == null || b == null) { + if (!a || !b) { return true; } if (a.length !== b.length) { return true; } - for (var i = 0; i < a.length; i++) { + for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return true; } diff --git a/lib/extract/extractBrush.js b/lib/extract/extractBrush.js index 039ec0f4c..ffa2fb338 100644 --- a/lib/extract/extractBrush.js +++ b/lib/extract/extractBrush.js @@ -2,8 +2,7 @@ import Color from 'color'; import patternReg from './patternReg'; export default function (colorOrBrush) { - /*eslint eqeqeq:0*/ - if (colorOrBrush === 'none' || colorOrBrush == null) { + if (colorOrBrush === 'none' || !colorOrBrush) { return null; } diff --git a/lib/extract/extractFill.js b/lib/extract/extractFill.js index 430c77d0c..b2fd7c228 100644 --- a/lib/extract/extractFill.js +++ b/lib/extract/extractFill.js @@ -18,8 +18,7 @@ export default function(props, styleProperties) { return { // default fill is black - /*eslint eqeqeq:0*/ - fill: extractBrush(props.fill == null ? '#000' : props.fill), + fill: extractBrush(props.fill || '#000'), fillOpacity: extractOpacity(props.fillOpacity), fillRule: fillRules[props.fillRule] === 0 ? 0 : 1 }; diff --git a/lib/extract/extractGradient.js b/lib/extract/extractGradient.js index 7befb7fea..41aeb4d96 100644 --- a/lib/extract/extractGradient.js +++ b/lib/extract/extractGradient.js @@ -21,6 +21,7 @@ export default function(props) { let offset = percentToFloat(child.props.offset); // add stop + //noinspection JSUnresolvedFunction stops[offset] = Color(child.props.stopColor).alpha(extractOpacity(child.props.stopOpacity)); } } else { diff --git a/lib/extract/extractProps.js b/lib/extract/extractProps.js index 1fd574b00..c1be5c443 100644 --- a/lib/extract/extractProps.js +++ b/lib/extract/extractProps.js @@ -30,7 +30,7 @@ export default function(props, ref) { let transform = props.transform; if (transform) { if (typeof transform === 'string') { - var transformParsed = tp.parse(transform); + const transformParsed = tp.parse(transform); if (transformParsed.matrix) { // TODO: Extract scaling values for coordinate system // Especially scaleY for calculating scaling of fontSize diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index a4c1ebdcd..ccc93b71f 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -44,7 +44,7 @@ export default function(props, styleProperties) { strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeDasharray: strokeDasharray || null, - strokeWidth: strokeWidth || "1", + strokeWidth: strokeWidth || '1', strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, strokeMiterlimit: props.strokeMiterlimit || 4 }; diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 214f297c2..11b9be37c 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +//noinspection JSUnresolvedVariable import React, {Children} from 'react'; import TSpan from '../../elements/TSpan'; @@ -28,7 +29,7 @@ function parseFontString(font) { return null; } const fontFamily = extractSingleFontFamily(match[3]); - const fontSize = match[2] || "12"; + const fontSize = match[2] || '12'; const isBold = /bold/.exec(match[1]); const isItalic = /italic/.exec(match[1]); const fontWeight = isBold ? 'bold' : 'normal'; diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index fb097eb2a..7f1b23292 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -15,11 +15,11 @@ function transformToMatrix(props, transform) { class TransformParser { constructor() { - var floating = '(\\-?[\\d\\.e]+)'; - var commaSpace = '\\,?\\s*'; + const floating = '(\\-?[\\d\\.e]+)'; + const commaSpace = '\\,?\\s*'; this.regex = { - split: /[\s*(\s*|\s*)\s*|\s*,\s*]/, + split: /[\s*()|,]/, matrix: new RegExp( '^matrix\\(' + floating + commaSpace + @@ -33,7 +33,7 @@ class TransformParser { parse(transform) { if (transform) { - var retval = {}; + const retval = {}; let transLst = _.filter( transform.split(this.regex.split), (ele) => { @@ -50,11 +50,11 @@ class TransformParser { break; case 'translate': retval.translateX = transLst[i + 1]; - retval.translateY = (3 === transLst.length) ? transLst[i + 2] : 0; + retval.translateY = (transLst.length === 3) ? transLst[i + 2] : 0; break; case 'scale': retval.scaleX = transLst[i + 1]; - retval.scaleY = (3 === transLst.length) ? transLst[i + 2] : retval.scaleX; + retval.scaleY = (transLst.length === 3) ? transLst[i + 2] : retval.scaleX; break; case 'rotate': retval.rotation = transLst[i + 1]; @@ -72,17 +72,6 @@ class TransformParser { return retval; } } - - parseMatrix(transform) { - var matrix = this.regex.matrix.exec(transform); - if (matrix) { - matrix.shift(); - for (var i = matrix.length - 1; i >= 0; i--) { - matrix[i] = parseFloat(matrix[i]); - } - } - return matrix; - } } export const tp = new TransformParser(); @@ -91,7 +80,7 @@ export const tp = new TransformParser(); function appendTransform(transform) { if (transform) { if (typeof transform === 'string') { - var transformParsed = tp.parse(transform); + const transformParsed = tp.parse(transform); if (transformParsed.matrix) { pooledMatrix.append(...transformParsed.matrix); } diff --git a/package.json b/package.json index 770ebdd2e..800fb38bd 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "babel-eslint": "^6.1.2", "eslint": "^2.13.1", "eslint-plugin-react": "^4.3.0", - "react-native": "^0.46.0" + "prop-types": "^15.5.10", + "react": "^16.0.0-alpha.12", + "react-native": ">=0.46.0" }, "nativePackage": true } From a14f2d0ca0581c7205b281a4008af3504e1ba68a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 00:16:17 +0300 Subject: [PATCH 119/198] Fix fontDiffer --- lib/attributes.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/attributes.js b/lib/attributes.js index eec6c0a0a..a62728074 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -19,13 +19,19 @@ function fontDiffer(a, b) { return false; } - return a.fontSize !== b.fontSize || - a.fontFamily !== b.fontFamily || + return ( a.fontStyle !== b.fontStyle || + a.fontVariant !== b.fontVariant || a.fontWeight !== b.fontWeight || - a.kerning !== b.kerning || + a.fontStretch !== b.fontStretch || + a.fontSize !== b.fontSize || + a.fontFamily !== b.fontFamily || + a.textAnchor !== b.textAnchor || + a.textDecoration !== b.textDecoration || + a.letterSpacing !== b.letterSpacing || a.wordSpacing !== b.wordSpacing || - a.letterSpacing !== b.letterSpacing; + a.kerning !== b.kerning + ); } const ViewBoxAttributes = { From 30ef288ded6ed46f47076daecf31caf21d6c9387 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 03:04:55 +0300 Subject: [PATCH 120/198] Fix peerDep --- android/src/main/java/com/horcrux/svg/TextShadowNode.java | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 4ee00ba84..3b44984ad 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -85,6 +85,8 @@ public void setFont(@Nullable ReadableMap font) { markUpdated(); } + // TODO implement https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm + @Override public void draw(Canvas canvas, Paint paint, float opacity) { if (opacity > MIN_OPACITY_FOR_DRAW) { diff --git a/package.json b/package.json index 800fb38bd..f718a4d35 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "^2.13.1", "eslint-plugin-react": "^4.3.0", "prop-types": "^15.5.10", - "react": "^16.0.0-alpha.12", + "react": "16.0.0-alpha.12", "react-native": ">=0.46.0" }, "nativePackage": true From c502c67968e2247dce9e2de48016bbe2783ebb30 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 03:26:56 +0300 Subject: [PATCH 121/198] Fix TextAttributes differ --- lib/attributes.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/attributes.js b/lib/attributes.js index a62728074..59c1fe197 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -112,13 +112,11 @@ const TextAttributes = { font: { diff: fontDiffer }, - textAnchor: true, - textDecoration: true, deltaX: arrayDiffer, deltaY: arrayDiffer, rotate: arrayDiffer, - positionX: true, - positionY: true, + positionX: arrayDiffer, + positionY: arrayDiffer, ...RenderableAttributes }; From 714768c1a7f482f581eb272a8592a101211fd6cb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 03:33:34 +0300 Subject: [PATCH 122/198] Fix TextAnchor prop-type definition. --- lib/props.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/props.js b/lib/props.js index 7523aeec8..b3d39a00a 100644 --- a/lib/props.js +++ b/lib/props.js @@ -94,7 +94,7 @@ const font = PropTypes.object; // start | middle | end | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor -const textAnchor = PropTypes.oneOf(['start', 'middle', 'end', 'inherit']); +const textAnchor = PropTypes.oneOf(['start', 'middle', 'end']); // none | underline | overline | line-through | blink | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration From e2128400e33d8d816b8277d43846315aa4e4103b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 04:46:15 +0300 Subject: [PATCH 123/198] Add TextLayoutAlgorithm.java --- .../com/horcrux/svg/TextLayoutAlgorithm.java | 963 ++++++++++++++++++ .../java/com/horcrux/svg/TextShadowNode.java | 2 - 2 files changed, 963 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java diff --git a/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java b/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java new file mode 100644 index 000000000..dea6cb4c0 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java @@ -0,0 +1,963 @@ +package com.horcrux.svg; + +// TODO implement https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm + +public class TextLayoutAlgorithm { + void layoutText() { +/* + + Setup + + + Let root be the result of generating + typographic character positions for the + ‘text’ element and its subtree, laid out as if it + were an absolutely positioned element. + + This will be a single line of text unless the + white-space property causes line breaks. + + + + Let count be the number of DOM characters + within the ‘text’ element's subtree. + + + Let result be an array of length count + whose entries contain the per-character information described + above. Each entry is initialized as follows: + + its global index number equal to its position in the array, + its "x" coordinate set to "unspecified", + its "y" coordinate set to "unspecified", + its "rotate" coordinate set to "unspecified", + its "hidden" flag is false, + its "addressable" flag is true, + its "middle" flag is false, + its "anchored chunk" flag is false. + + If result is empty, then return result. + + + Let CSS_positions be an array of length + count whose entries will be filled with the + x and y positions of the corresponding + typographic character in root. The array + entries are initialized to (0, 0). + + + Let "horizontal" be a flag, true if the writing mode of ‘text’ + is horizontal, false otherwise. + + + + + Set flags and assign initial positions + + For each array element with index i in + result: + + + + Set addressable to false if the character at index i was: + + + part of the text content of a non-rendered element + + + discarded during layout due to being a + collapsed + white space character, a soft hyphen character, or a + bidi control character; or + + + discarded during layout due to being a + collapsed + segment break; or + + + trimmed + from the start or end of a line. + + + + + Since there is collapsible white space not addressable by glyph + positioning attributes in the following ‘text’ element + (with a standard font), the "B" glyph will be placed at x=300. + + + A + B + + + This is because the white space before the "A", and all but one white space + character between the "A" and "B", is collapsed away or trimmed. + + + + + Set middle to true if the character at index i + is the second or later character that corresponds to a typographic character. + + + If the character at index i corresponds to a typographic character at the beginning of a line, then set the "anchored + chunk" flag of result[i] to true. + + This ensures chunks shifted by text-anchor do not + span multiple lines. + + + + If addressable is true and middle is false then + set CSS_positions[i] to the position of the + corresponding typographic character as determined by the CSS + renderer. Otherwise, if i > 0, then set + CSS_positions[i] = + CSS_positions[i − 1] + + + + + Resolve character positioning + + Position adjustments (e.g values in a ‘x’ attribute) + specified by a node apply to all characters in that node including + characters in the node's descendants. Adjustments specified in + descendant nodes, however, override adjustments from ancestor + nodes. This section resolves which adjustments are to be applied to + which characters. It also directly sets the rotate coordinate + of result. + + + + Set up: + + + Let resolve_x, resolve_y, + resolve_dx, and resolve_dy be arrays of + length count whose entries are all initialized + to "unspecified". + + + Set "in_text_path" flag false. + + This flag will allow ‘y’ (‘x’) + attribute values to be ignored for horizontal (vertical) + text inside ‘textPath’ elements. + + + + Call the following procedure with the ‘text’ element node. + + + + + Procedure: resolve character + positioning: + + A recursive procedure that takes as input a node and + whose steps are as follows: + + + + If node is a ‘text’ or ‘tspan’ node: + + + Let index equal the "global index number" of the + first character in the node. + + + Let x, y, dx, dy + and rotate be the lists of values from the + corresponding attributes on node, or empty + lists if the corresponding attribute was not specified + or was invalid. + + + If "in_text_path" flag is false: + + + Let new_chunk_count + = max(length of x, length of y). + + + Else: + + + If the "horizontal" flag is true: + + + Let new_chunk_count = length of x. + + + + + Else: + + + Let new_chunk_count = length of y. + + + + + + + Let length be the number of DOM characters in the + subtree rooted at node. + + + Let i = 0 and j = 0. + + i is an index of addressable characters in the node; + j is an index of all characters in the node. + + + + While j < length, do: + + This loop applies the ‘x’, ‘y’, + ‘dx’, ‘dy’ and ‘rotate’ + attributes to the content inside node. + + + + If the "addressable" flag of result[index + + j] is true, then: + + + If i < new_check_count, then + set the "anchored chunk" flag of + result[index + j] to + true. Else set the flag to false. + + Setting the flag to false ensures that ‘x’ + and ‘y’ attributes set in a ‘text’ + element don't create anchored chunk in a ‘textPath’ + element when they should not. + + + + If i < length of x, + then set resolve_x[index + + j] to x[i]. + + + If "in_text_path" flag is true and the "horizontal" + flag is false, unset + resolve_x[index]. + + The ‘x’ attribute is ignored for + vertical text on a path. + + + + If i < length of y, + then set resolve_y[index + + j] to y[i]. + + + If "in_text_path" flag is true and the "horizontal" + flag is true, unset + resolve_y[index]. + + The ‘y’ attribute is ignored for + horizontal text on a path. + + + + If i < length of dx, + then set resolve_dx[index + + j] to dy[i]. + + + If i < length of dy, + then set resolve_dy[index + + j] to dy[i]. + + + If i < length of rotate, + then set the angle value of result[index + + j] to rotate[i]. + Otherwise, if rotate is not empty, then + set result[index + j] + to result[index + j − 1]. + + + Set i = i + 1. + + + + + Set j = j + 1. + + + + + + + If node is a ‘textPath’ node: + + + Let index equal the global index number of the + first character in the node (including descendant nodes). + + + Set the "anchored chunk" flag of result[index] + to true. + + A ‘textPath’ element always creates an anchored chunk. + + + + Set in_text_path flag true. + + + + + For each child node child of node: + + + Resolve glyph + positioning of child. + + + + + If node is a ‘textPath’ node: + + + Set "in_text_path" flag false. + + + + + + + + + Adjust positions: dx, dy + + The ‘dx’ and ‘dy’ adjustments are applied + before adjustments due to the ‘textLength’ attribute while + the ‘x’, ‘y’ and ‘rotate’ + adjustments are applied after. + + + + Let shift be the cumulative x and + y shifts due to ‘x’ and ‘y’ + attributes, initialized to (0,0). + + + For each array element with index i in result: + + + If resolve_x[i] is unspecified, set it to 0. + If resolve_y[i] is unspecified, set it to 0. + + + Let shift.x = shift.x + resolve_x[i] + and shift.y = shift.y + resolve_y[i]. + + + Let result[i].x = CSS_positions[i].x + shift.x + and result[i].y = CSS_positions[i].y + shift.y. + + + + + + + Apply ‘textLength’ attribute + + + Set up: + + + Define resolved descendant node as a + descendant of node with a valid ‘textLength’ + attribute that is not itself a descendant node of a + descendant node that has a valid ‘textLength’ + attribute. + + + Call the following procedure with the ‘text’ element + node. + + + + + Procedure: resolve text length: + + A recursive procedure that takes as input + a node and whose steps are as follows: + + + + For each child node child of node: + + + Resolve text length of child. + + Child nodes are adjusted before parent nodes. + + + + + + If node is a ‘text’ or ‘tspan’ node + and if the node has a valid ‘textLength’ attribute value: + + + Let a = +∞ and b = −∞. + + + Let i and j be the global + index of the first character and last characters + in node, respectively. + + + For each index k in the range + [i, j] where the "addressable" flag + of result[k] is true: + + This loop finds the left-(top-) most and + right-(bottom-) most extents of the typographic characters within the node and checks for + forced line breaks. + + + + If the character at k is a linefeed + or carriage return, return. No adjustments due to + ‘textLength’ are made to a node with + a forced line break. + + + Let pos = the x coordinate of the position + in result[k], if the "horizontal" + flag is true, and the y coordinate otherwise. + + + Let advance = the advance of + the typographic character corresponding to + character k. [NOTE: This advance will be + negative for RTL horizontal text.] + + + Set a = + min(a, pos, pos + + advance). + + + Set b = + max(b, pos, pos + + advance). + + + + + If a ≠ +∞ then: + + + Find the distance delta = ‘textLength’ + computed value − (b − a). + + User agents are required to shift the last + typographic character in the node by + delta, in the positive x direction + if the "horizontal" flag is true and if + direction is + lrt, in the + negative x direction if the "horizontal" flag + is true and direction is + rtl, or in the + positive y direction otherwise. User agents + are free to adjust intermediate + typographic characters for optimal + typography. The next steps indicate one way to + adjust typographic characters when + the value of ‘lengthAdjust’ is + spacing. + + + + Find n, the total number of + typographic characters in this node + including any descendant nodes that are not resolved + descendant nodes or within a resolved descendant + node. + + + Let n = n + number of + resolved descendant nodes − 1. + + Each resolved descendant node is treated as if it + were a single + typographic character in this + context. + + + + Find the per-character adjustment δ + = delta/n. + + + Let shift = 0. + + + For each index k in the range [i,j]: + + + Add shift to the x coordinate of the + position in result[k], if the "horizontal" + flag is true, and to the y coordinate + otherwise. + + + If the "middle" flag for result[k] + is not true and k is not a character in + a resolved descendant node other than the first + character then shift = shift + + δ. + + + + + + + + + + + + + Adjust positions: x, y + + This loop applies ‘x’ and ‘y’ values, + and ensures that text-anchor chunks do not start in + the middle of a typographic character. + + + + Let shift be the current adjustment due to + the ‘x’ and ‘y’ attributes, + initialized to (0,0). + + + Set index = 1. + + + While index < count: + + + If resolved_x[index] is set, then let + shift.x = + resolved_x[index] − + result.x[index]. + + + If resolved_y[index] is set, then let + shift.y = + resolved_y[index] − + result.y[index]. + + + Let result.x[index] = + result.x[index] + shift.x + and result.y[index] = + result.y[index] + shift.y. + + + If the "middle" and "anchored chunk" flags + of result[index] are both true, then: + + + Set the "anchored chunk" flag + of result[index] to false. + + + If index + 1 < count, then set + the "anchored chunk" flag + of result[index + 1] to true. + + + + + Set index to index + 1. + + + + + + + Apply anchoring + + + For each slice result[i..j] + (inclusive of both i and j), where: + + + the "anchored chunk" flag of result[i] + is true, + + + the "anchored chunk" flags + of result[k] where i + < k ≤ j are false, and + + + j = count − 1 or the "anchored + chunk" flag of result[j + 1] is + true; + + + do: + + This loops over each anchored chunk. + + + + Let a = +∞ and b = −∞. + + + For each index k in the range + [i, j] where the "addressable" flag + of result[k] is true: + + This loop finds the left-(top-) most and + right-(bottom-) most extents of the typographic character within the anchored chunk. + + + + Let pos = the x coordinate of the position + in result[k], if the "horizontal" flag + is true, and the y coordinate otherwise. + + + Let advance = the advance of + the typographic character corresponding to + character k. [NOTE: This advance will be + negative for RTL horizontal text.] + + + Set a = + min(a, pos, pos + + advance). + + + Set b = + max(b, pos, pos + + advance). + + + + + If a ≠ +∞, then: + + Here we perform the text anchoring. + + + + Let shift be the x coordinate of + result[i], if the "horizontal" flag + is true, and the y coordinate otherwise. + + + Adjust shift based on the value of text-anchor + and direction of the element the character at + index i is in: + + (start, ltr) or (end, rtl) + Set shift = shift − a. + (start, rtl) or (end, ltr) + Set shift = shift − b. + (middle, ltr) or (middle, rtl) + Set shift = shift − (a + b) / 2. + + + + For each index k in the range [i, j]: + + + Add shift to the x coordinate of the position + in result[k], if the "horizontal" + flag is true, and to the y coordinate otherwise. + + + + + + + + + + + Position on path + + + Set index = 0. + + + Set the "in path" flag to false. + + + Set the "after path" flag to false. + + + Let path_end be an offset for characters that follow + a ‘textPath’ element. Set path_end to (0,0). + + + While index < count: + + + If the character at index i is within a + ‘textPath’ element and corresponds to a typographic character, then: + + + Set "in path" flag to true. + + + If the "middle" flag of + result[index] is false, then: + + Here we apply ‘textPath’ positioning. + + + + Let path be the equivalent path of + the basic shape element referenced by + the ‘textPath’ element, or an empty path if + the reference is invalid. + + + If the ‘side’ attribute of + the ‘textPath’ element is + 'right', then + reverse path. + + + Let length be the length + of path. + + + Let offset be the value of the + ‘textPath’ element's + ‘startOffset’ attribute, adjusted + due to any ‘pathLength’ attribute on the + referenced element (if the referenced element is + a ‘path’ element). + + + Let advance = the advance of + the typographic character corresponding + to character k. [NOTE: This advance will + be negative for RTL horizontal text.] + + + Let (x, y) + and angle be the position and angle + in result[index]. + + + Let mid be a coordinate value depending + on the value of the "horizontal" flag: + + true + mid is x + advance / 2 + + offset + false + mid is y + advance / 2 + + offset + + + The user agent is free to make any additional adjustments to + mid necessary to ensure high quality typesetting + due to a ‘spacing’ value of + 'auto' or a + ‘method’ value of + 'stretch'. + + + + If path is not a closed subpath and + mid < 0 or mid > length, + set the "hidden" flag of result[index] to true. + + + If path is a closed subpath depending on + the values of text-anchor and direction of + the element the character at index is in: + + This implements the special wrapping criteria for single + closed subpaths. + + + (start, ltr) or (end, rtl) + + If mid−offset < 0 + or mid−offset > length, + set the "hidden" flag of result[index] to true. + + (middle, ltr) or (middle, rtl) + + If + If mid−offset < −length/2 + or mid−offset > length/2, + set the "hidden" flag of result[index] to true. + + (start, rtl) or (end, ltr) + + If mid−offset < −length + or mid−offset > 0, + set the "hidden" flag of result[index] to true. + + + + Set mid = mid mod length. + + + If the hidden flag is false: + + + Let point be the position and + t be the unit vector tangent to + the point mid distance + along path. + + + If the "horizontal" flag is + + true + + + + Let n be the normal unit vector + pointing in the direction t + 90°. + + + Let o be the horizontal distance from the + vertical center line of the glyph to the alignment point. + + + Then set the position in + result[index] to + point - + o×t + + y×n. + + + Let r be the angle from + the positive x-axis to the tangent. + + + Set the angle value + in result[index] + to angle + r. + + + + false + + + + Let n be the normal unit vector + pointing in the direction t - 90°. + + + Let o be the vertical distance from the + horizontal center line of the glyph to the alignment point. + + + Then set the position in + result[index] to + point - + o×t + + x×n. + + + Let r be the angle from + the positive y-axis to the tangent. + + + Set the angle value + in result[index] + to angle + r. + + + + + + + + + + + Otherwise, the "middle" flag + of result[index] is true: + + + Set the position and angle values + of result[index] to those + in result[index − 1]. + + + + + + + If the character at index i is not within a + ‘textPath’ element and corresponds to a typographic character, then: + + This sets the starting point for rendering any characters that + occur after a ‘textPath’ element to the end of the path. + + + If the "in path" flag is true: + + + Set the "in path" flag to false. + + + Set the "after path" flag to true. + + + Set path_end equal to the end point of the path + referenced by ‘textPath’ − the position of + result[index]. + + + + + If the "after path" is true. + + + If anchored chunk of + result[index] is true, set the + "after path" flag to false. + + + Else, + let result.x[index] = + result.x[index] + path_end.x + and result.y[index] = + result.y[index] + path_end.y. + + + + + + + Set index = index + 1. + + + + + + + Return result + + */ + } +} diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 3b44984ad..4ee00ba84 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -85,8 +85,6 @@ public void setFont(@Nullable ReadableMap font) { markUpdated(); } - // TODO implement https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm - @Override public void draw(Canvas canvas, Paint paint, float opacity) { if (opacity > MIN_OPACITY_FOR_DRAW) { From 7c5a7ceed3cce1813c05d8f612d1608dad2c34f7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 18:02:26 +0300 Subject: [PATCH 124/198] Implement FontData class instead of WritableMap / ReadableMap --- .../main/java/com/horcrux/svg/FontData.java | 97 +++++++++++++++++ .../java/com/horcrux/svg/GlyphContext.java | 102 +++--------------- .../java/com/horcrux/svg/TSpanShadowNode.java | 63 ++++------- .../java/com/horcrux/svg/TextShadowNode.java | 6 -- .../java/com/horcrux/svg/VirtualNode.java | 2 +- 5 files changed, 134 insertions(+), 136 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/FontData.java diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java new file mode 100644 index 000000000..2f190b96b --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -0,0 +1,97 @@ +package com.horcrux.svg; + +import com.facebook.react.bridge.ReadableMap; + +import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY; +import static com.facebook.react.uimanager.ViewProps.FONT_SIZE; +import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT; + +class FontData { + static final double DEFAULT_FONT_SIZE = 12d; + + private static final double DEFAULT_KERNING = 0d; + private static final double DEFAULT_WORD_SPACING = 0d; + private static final double DEFAULT_LETTER_SPACING = 0d; + + private static final String KERNING = "kerning"; + private static final String TEXT_ANCHOR = "textAnchor"; + private static final String WORD_SPACING = "wordSpacing"; + private static final String LETTER_SPACING = "letterSpacing"; + private static final String TEXT_DECORATION = "textDecoration"; + + final double fontSize; + + final String fontStyle; + final String fontFamily; + final String fontWeight; + + final String textAnchor; + final String textDecoration; + + final double kerning; + final double wordSpacing; + final double letterSpacing; + + final boolean manualKerning; + + static final FontData Defaults = new FontData(); + + private FontData() { + fontStyle = ""; + fontFamily = ""; + fontWeight = ""; + + textAnchor = "start"; + textDecoration = "none"; + + manualKerning = false; + kerning = DEFAULT_KERNING; + fontSize = DEFAULT_FONT_SIZE; + wordSpacing = DEFAULT_WORD_SPACING; + letterSpacing = DEFAULT_LETTER_SPACING; + } + + private double toAbsolute(String string, double scale, double fontSize) { + return PropHelper.fromRelative( + string, + 0, + 0, + scale, + fontSize + ); + } + + FontData(ReadableMap font, FontData parent, double scale) { + double parentFontSize = parent.fontSize; + + if (font.hasKey(FONT_SIZE)) { + String string = font.getString(FONT_SIZE); + fontSize = PropHelper.fromRelative( + string, + parentFontSize, + 0, + 1, + parentFontSize + ); + } else { + fontSize = parentFontSize; + } + + fontStyle = font.hasKey(FONT_SIZE) ? font.getString(FONT_SIZE) : parent.fontStyle; + fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily; + fontWeight = font.hasKey(FONT_WEIGHT) ? font.getString(FONT_WEIGHT) : parent.fontWeight; + + textAnchor = font.hasKey(TEXT_ANCHOR) ? font.getString(TEXT_ANCHOR) : parent.textAnchor; + textDecoration = font.hasKey(TEXT_DECORATION) ? font.getString(TEXT_DECORATION) : parent.textDecoration; + + final boolean hasKerning = font.hasKey(KERNING); + manualKerning = hasKerning || parent.manualKerning; + + // https://www.w3.org/TR/SVG11/text.html#SpacingProperties + // https://drafts.csswg.org/css-text-3/#spacing + // calculated values for units in: kerning, word-spacing, and, letter-spacing. + kerning = hasKerning ? toAbsolute(font.getString(KERNING), scale, fontSize) : parent.kerning; + wordSpacing = font.hasKey(WORD_SPACING) ? toAbsolute(font.getString(WORD_SPACING), scale, fontSize) : parent.wordSpacing; + letterSpacing = font.hasKey(LETTER_SPACING) ? toAbsolute(font.getString(LETTER_SPACING), scale, fontSize) : parent.letterSpacing; + } +} diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 5d4bdb81a..92b5d55a5 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -9,38 +9,20 @@ package com.horcrux.svg; -import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; import java.util.ArrayList; import javax.annotation.Nullable; -import static com.horcrux.svg.TextShadowNode.KERNING; -import static com.horcrux.svg.TextShadowNode.TEXT_ANCHOR; -import static com.horcrux.svg.TextShadowNode.WORD_SPACING; -import static com.horcrux.svg.TextShadowNode.LETTER_SPACING; -import static com.horcrux.svg.TextShadowNode.TEXT_DECORATION; -import static com.facebook.react.uimanager.ViewProps.FONT_SIZE; -import static com.facebook.react.uimanager.ViewProps.FONT_STYLE; -import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY; -import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT; +import static com.horcrux.svg.FontData.DEFAULT_FONT_SIZE; // https://www.w3.org/TR/SVG/text.html#TSpanElement class GlyphContext { - static final double DEFAULT_FONT_SIZE = 12d; - - // Empty font context map - private static final WritableMap DEFAULT_MAP = Arguments.createMap(); - - static { - DEFAULT_MAP.putDouble(FONT_SIZE, DEFAULT_FONT_SIZE); - } // Current stack (one per node push/pop) - private final ArrayList mFontContext = new ArrayList<>(); + private final ArrayList mFontContext = new ArrayList<>(); // Unique input attribute lists (only added if node sets a value) private final ArrayList mXsContext = new ArrayList<>(); @@ -65,7 +47,7 @@ class GlyphContext { // Calculated on push context, percentage and em length depends on parent font size private double mFontSize = DEFAULT_FONT_SIZE; - private ReadableMap topFont = DEFAULT_MAP; + private FontData topFont = FontData.Defaults; // Current accumulated values // https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate @@ -137,8 +119,6 @@ private void pushIndices() { mWidth = width; mHeight = height; - mFontContext.add(DEFAULT_MAP); - mXsContext.add(mXs); mYsContext.add(mYs); mDXsContext.add(mDXs); @@ -151,6 +131,8 @@ private void pushIndices() { mDYIndices.add(mDYIndex); mRIndices.add(mRIndex); + mFontContext.add(topFont); + pushIndices(); } @@ -160,54 +142,30 @@ private void reset() { mX = mY = mDX = mDY = 0; } - ReadableMap getFont() { + FontData getFont() { return topFont; } - private ReadableMap getTopOrParentFont(GroupShadowNode child) { + private FontData getTopOrParentFont(GroupShadowNode child) { if (mTop > 0) { return topFont; } else { GroupShadowNode parentRoot = child.getParentTextRoot(); while (parentRoot != null) { - ReadableMap map = parentRoot.getGlyphContext().getFont(); - if (map != DEFAULT_MAP) { + FontData map = parentRoot.getGlyphContext().getFont(); + if (map != FontData.Defaults) { return map; } parentRoot = parentRoot.getParentTextRoot(); } - return DEFAULT_MAP; - } - } - - private static void put(String key, WritableMap map, ReadableMap font, ReadableMap parent) { - if (font.hasKey(key)) { - map.putString(key, font.getString(key)); - } else if (parent.hasKey(key)) { - map.putString(key, parent.getString(key)); - } - } - - private void putD(String key, WritableMap map, ReadableMap font, ReadableMap parent) { - if (font.hasKey(key)) { - String string = font.getString(key); - double value = PropHelper.fromRelative( - string, - 0, - 0, - mScale, - mFontSize - ); - map.putDouble(key, value); - } else if (parent.hasKey(key)) { - map.putDouble(key, parent.getDouble(key)); + return FontData.Defaults; } } private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { - ReadableMap parent = getTopOrParentFont(node); + FontData parent = getTopOrParentFont(node); mTop++; if (font == null) { @@ -215,42 +173,10 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { return; } - WritableMap map = Arguments.createMap(); - mFontContext.add(map); - topFont = map; - - double parentFontSize = parent.getDouble(FONT_SIZE); - - if (font.hasKey(FONT_SIZE)) { - String string = font.getString(FONT_SIZE); - double value = PropHelper.fromRelative( - string, - parentFontSize, - 0, - 1, - parentFontSize - ); - map.putDouble(FONT_SIZE, value); - mFontSize = value; - } else { - mFontSize = parentFontSize; - } - - map.putDouble(FONT_SIZE, mFontSize); - - put(FONT_STYLE, map, font, parent); - put(FONT_FAMILY, map, font, parent); - put(FONT_WEIGHT, map, font, parent); - - put(TEXT_ANCHOR, map, font, parent); - put(TEXT_DECORATION, map, font, parent); + FontData data = new FontData(font, parent, mScale); + mFontContext.add(data); + topFont = data; - // https://www.w3.org/TR/SVG11/text.html#SpacingProperties - // https://drafts.csswg.org/css-text-3/#spacing - // calculated values for units in: kerning, word-spacing, and, letter-spacing. - putD(KERNING, map, font, parent); - putD(WORD_SPACING, map, font, parent); - putD(LETTER_SPACING, map, font, parent); } void pushContext(GroupShadowNode node, @Nullable ReadableMap font) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index cc4250d2f..ae9c48e76 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -19,7 +19,6 @@ import android.graphics.RectF; import android.graphics.Typeface; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -28,11 +27,6 @@ import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG; import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG; -import static com.facebook.react.uimanager.ViewProps.FONT_SIZE; -import static com.facebook.react.uimanager.ViewProps.FONT_STYLE; -import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT; -import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY; - /** * Shadow node for virtual TSpan view */ @@ -45,10 +39,6 @@ class TSpanShadowNode extends TextShadowNode { private static final String OTF = ".otf"; private static final String TTF = ".ttf"; - private static final double DEFAULT_KERNING = 0d; - private static final double DEFAULT_WORD_SPACING = 0d; - private static final double DEFAULT_LETTER_SPACING = 0d; - private Path mCache; private @Nullable String mContent; private TextPathShadowNode textPath; @@ -119,14 +109,13 @@ private Path getLinePath(String line, Paint paint) { } GlyphContext gc = getTextRootGlyphContext(); - ReadableMap font = gc.getFont(); + FontData font = gc.getFont(); applyTextPropertiesToPaint(paint, font); double distance = 0; double renderMethodScaling = 1; final double textMeasure = paint.measureText(line); - double offset = font.hasKey(TEXT_ANCHOR) ? - getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR)) : 0; + double offset = getTextAnchorShift(textMeasure, font.textAnchor); PathMeasure pm = null; if (textPath != null) { @@ -176,17 +165,12 @@ private Path getLinePath(String line, Paint paint) { * * */ - final boolean hasKerning = font.hasKey(KERNING); - double kerning = hasKerning ? font.getDouble(KERNING) : DEFAULT_KERNING; - final boolean autoKerning = !hasKerning; + final boolean autoKerning = !font.manualKerning; + double kerning = font.kerning; - final double wordSpacing = font.hasKey(WORD_SPACING) ? - font.getDouble(WORD_SPACING) - : DEFAULT_WORD_SPACING; + final double wordSpacing = font.wordSpacing; - final double letterSpacing = font.hasKey(LETTER_SPACING) ? - font.getDouble(LETTER_SPACING) - : DEFAULT_LETTER_SPACING; + final double letterSpacing = font.letterSpacing; for (int index = 0; index < length; index++) { glyph = new Path(); @@ -249,31 +233,27 @@ private Path getLinePath(String line, Paint paint) { return path; } - private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { + private void applyTextPropertiesToPaint(Paint paint, FontData font) { AssetManager assetManager = getThemedContext().getResources().getAssets(); - double fontSize = font.getDouble(FONT_SIZE) * mScale; + double fontSize = font.fontSize * mScale; - boolean isBold = font.hasKey(FONT_WEIGHT) && - BOLD.equals(font.getString(FONT_WEIGHT)); + boolean isBold = BOLD.equals(font.fontWeight); - boolean isItalic = font.hasKey(FONT_STYLE) && - ITALIC.equals(font.getString(FONT_STYLE)); + boolean isItalic = ITALIC.equals(font.fontStyle); boolean underlineText = false; boolean strikeThruText = false; - if (font.hasKey(TEXT_DECORATION)) { - String decoration = font.getString(TEXT_DECORATION); - switch (decoration) { - case TEXT_DECORATION_UNDERLINE: - underlineText = true; - break; + String decoration = font.textDecoration; + switch (decoration) { + case TEXT_DECORATION_UNDERLINE: + underlineText = true; + break; - case TEXT_DECORATION_LINE_THROUGH: - strikeThruText = true; - break; - } + case TEXT_DECORATION_LINE_THROUGH: + strikeThruText = true; + break; } int fontStyle; @@ -288,16 +268,17 @@ private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { } Typeface typeface = null; + final String fontFamily = font.fontFamily; try { - String path = FONTS + font.getString(FONT_FAMILY) + OTF; + String path = FONTS + fontFamily + OTF; typeface = Typeface.createFromAsset(assetManager, path); } catch (Exception ignored) { try { - String path = FONTS + font.getString(FONT_FAMILY) + TTF; + String path = FONTS + fontFamily + TTF; typeface = Typeface.createFromAsset(assetManager, path); } catch (Exception ignored2) { try { - typeface = Typeface.create(font.getString(FONT_FAMILY), fontStyle); + typeface = Typeface.create(fontFamily, fontStyle); } catch (Exception ignored3) { } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 4ee00ba84..443bb2eee 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -37,12 +37,6 @@ class TextShadowNode extends GroupShadowNode { static final String TEXT_DECORATION_LINE_THROUGH = "line-through"; // static final String TEXT_DECORATION_BLINK = "blink"; - static final String KERNING = "kerning"; - static final String TEXT_ANCHOR = "textAnchor"; - static final String WORD_SPACING = "wordSpacing"; - static final String LETTER_SPACING = "letterSpacing"; - static final String TEXT_DECORATION = "textDecoration"; - private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; private @Nullable ReadableArray mRotate; diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 594e3f688..754989f7d 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -26,7 +26,7 @@ import javax.annotation.Nullable; -import static com.horcrux.svg.GlyphContext.DEFAULT_FONT_SIZE; +import static com.horcrux.svg.FontData.DEFAULT_FONT_SIZE; abstract class VirtualNode extends LayoutShadowNode { /* From e99ce01dfe74383d6fa3b224ef6910aad00fb504 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 19:35:07 +0300 Subject: [PATCH 125/198] Implement FontData Enums --- .../main/java/com/horcrux/svg/FontData.java | 25 +++++----- .../main/java/com/horcrux/svg/FontStyle.java | 7 +++ .../main/java/com/horcrux/svg/FontWeight.java | 48 ++++++++++++++++++ .../java/com/horcrux/svg/TSpanShadowNode.java | 49 +++++++------------ .../main/java/com/horcrux/svg/TextAnchor.java | 8 +++ .../java/com/horcrux/svg/TextDecoration.java | 41 ++++++++++++++++ .../java/com/horcrux/svg/TextShadowNode.java | 13 ----- 7 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/FontStyle.java create mode 100644 android/src/main/java/com/horcrux/svg/FontWeight.java create mode 100644 android/src/main/java/com/horcrux/svg/TextAnchor.java create mode 100644 android/src/main/java/com/horcrux/svg/TextDecoration.java diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index 2f190b96b..7c10bb117 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -4,6 +4,7 @@ import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY; import static com.facebook.react.uimanager.ViewProps.FONT_SIZE; +import static com.facebook.react.uimanager.ViewProps.FONT_STYLE; import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT; class FontData { @@ -21,12 +22,12 @@ class FontData { final double fontSize; - final String fontStyle; final String fontFamily; - final String fontWeight; + final FontStyle fontStyle; + final FontWeight fontWeight; - final String textAnchor; - final String textDecoration; + final TextAnchor textAnchor; + final TextDecoration textDecoration; final double kerning; final double wordSpacing; @@ -37,12 +38,12 @@ class FontData { static final FontData Defaults = new FontData(); private FontData() { - fontStyle = ""; fontFamily = ""; - fontWeight = ""; + fontStyle = FontStyle.normal; + fontWeight = FontWeight.Normal; - textAnchor = "start"; - textDecoration = "none"; + textAnchor = TextAnchor.start; + textDecoration = TextDecoration.None; manualKerning = false; kerning = DEFAULT_KERNING; @@ -77,12 +78,12 @@ private double toAbsolute(String string, double scale, double fontSize) { fontSize = parentFontSize; } - fontStyle = font.hasKey(FONT_SIZE) ? font.getString(FONT_SIZE) : parent.fontStyle; fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily; - fontWeight = font.hasKey(FONT_WEIGHT) ? font.getString(FONT_WEIGHT) : parent.fontWeight; + fontStyle = font.hasKey(FONT_STYLE) ? FontStyle.valueOf(font.getString(FONT_STYLE)) : parent.fontStyle; + fontWeight = font.hasKey(FONT_WEIGHT) ? FontWeight.getEnum(font.getString(FONT_WEIGHT)) : parent.fontWeight; - textAnchor = font.hasKey(TEXT_ANCHOR) ? font.getString(TEXT_ANCHOR) : parent.textAnchor; - textDecoration = font.hasKey(TEXT_DECORATION) ? font.getString(TEXT_DECORATION) : parent.textDecoration; + textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor; + textDecoration = font.hasKey(TEXT_DECORATION) ? TextDecoration.getEnum(font.getString(TEXT_DECORATION)) : parent.textDecoration; final boolean hasKerning = font.hasKey(KERNING); manualKerning = hasKerning || parent.manualKerning; diff --git a/android/src/main/java/com/horcrux/svg/FontStyle.java b/android/src/main/java/com/horcrux/svg/FontStyle.java new file mode 100644 index 000000000..d17929337 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FontStyle.java @@ -0,0 +1,7 @@ +package com.horcrux.svg; + +enum FontStyle { + normal, + italic, + oblique +} diff --git a/android/src/main/java/com/horcrux/svg/FontWeight.java b/android/src/main/java/com/horcrux/svg/FontWeight.java new file mode 100644 index 000000000..11462043d --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FontWeight.java @@ -0,0 +1,48 @@ +package com.horcrux.svg; + +import com.facebook.common.internal.ImmutableMap; + +import java.util.HashMap; +import java.util.Map; + +enum FontWeight { + Normal ("normal"), + Bold ("bold"), + Bolder ("bolder"), + Lighter ("lighter"), + w100 ("100"), + w200 ("200"), + w300 ("300"), + w400 ("400"), + w500 ("500"), + w600 ("600"), + w700 ("700"), + w800 ("800"), + w900 ("900"); + + private final String weight; + FontWeight(String weight) { + this.weight = weight; + } + + public static FontWeight getEnum(String strVal) { + if(!weightToEnum.containsKey(strVal)) { + throw new IllegalArgumentException("Unknown String Value: " + strVal); + } + return weightToEnum.get(strVal); + } + + private static final Map weightToEnum; + static { + final Map tmpMap = new HashMap<>(); + for(final FontWeight en : FontWeight.values()) { + tmpMap.put(en.weight, en); + } + weightToEnum = ImmutableMap.copyOf(tmpMap); + } + + @Override + public String toString() { + return weight; + } +} diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index ae9c48e76..bc73bc2e1 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -31,11 +31,8 @@ * Shadow node for virtual TSpan view */ class TSpanShadowNode extends TextShadowNode { - private static final String STRETCH = "stretch"; - private static final String ITALIC = "italic"; private static final String FONTS = "fonts/"; - private static final String BOLD = "bold"; private static final String OTF = ".otf"; private static final String TTF = ".ttf"; @@ -85,21 +82,6 @@ protected Path getPath(Canvas canvas, Paint paint) { return mCache; } - private double getTextAnchorShift(double width, String textAnchor) { - double x = 0; - - switch (textAnchor) { - case TEXT_ANCHOR_MIDDLE: - x = -width / 2; - break; - case TEXT_ANCHOR_END: - x = -width; - break; - } - - return x; - } - private Path getLinePath(String line, Paint paint) { final int length = line.length(); final Path path = new Path(); @@ -115,7 +97,18 @@ private Path getLinePath(String line, Paint paint) { double distance = 0; double renderMethodScaling = 1; final double textMeasure = paint.measureText(line); - double offset = getTextAnchorShift(textMeasure, font.textAnchor); + + final TextAnchor textAnchor = font.textAnchor; + double offset; + if (textAnchor == TextAnchor.start) { + offset = 0; + } else { + if (textAnchor == TextAnchor.middle) { + offset = -textMeasure / 2; + } else { + offset = -textMeasure; + } + } PathMeasure pm = null; if (textPath != null) { @@ -238,22 +231,18 @@ private void applyTextPropertiesToPaint(Paint paint, FontData font) { double fontSize = font.fontSize * mScale; - boolean isBold = BOLD.equals(font.fontWeight); + boolean isBold = font.fontWeight == FontWeight.Bold; - boolean isItalic = ITALIC.equals(font.fontStyle); + boolean isItalic = font.fontStyle == FontStyle.italic; boolean underlineText = false; boolean strikeThruText = false; - String decoration = font.textDecoration; - switch (decoration) { - case TEXT_DECORATION_UNDERLINE: - underlineText = true; - break; - - case TEXT_DECORATION_LINE_THROUGH: - strikeThruText = true; - break; + TextDecoration decoration = font.textDecoration; + if (decoration == TextDecoration.Underline) { + underlineText = true; + } else if (decoration == TextDecoration.LineThrough) { + strikeThruText = true; } int fontStyle; diff --git a/android/src/main/java/com/horcrux/svg/TextAnchor.java b/android/src/main/java/com/horcrux/svg/TextAnchor.java new file mode 100644 index 000000000..b2aa76277 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextAnchor.java @@ -0,0 +1,8 @@ +package com.horcrux.svg; + +enum TextAnchor +{ + start, + middle, + end +} diff --git a/android/src/main/java/com/horcrux/svg/TextDecoration.java b/android/src/main/java/com/horcrux/svg/TextDecoration.java new file mode 100644 index 000000000..9669e1e9f --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextDecoration.java @@ -0,0 +1,41 @@ +package com.horcrux.svg; + +import com.facebook.common.internal.ImmutableMap; + +import java.util.HashMap; +import java.util.Map; + +enum TextDecoration +{ + None("none"), + Underline("underline"), + Overline("overline"), + LineThrough("line-through"), + Blink("blink"); + + private final String decoration; + TextDecoration(String decoration) { + this.decoration = decoration; + } + + public static TextDecoration getEnum(String strVal) { + if(!decorationToEnum.containsKey(strVal)) { + throw new IllegalArgumentException("Unknown String Value: " + strVal); + } + return decorationToEnum.get(strVal); + } + + private static final Map decorationToEnum; + static { + final Map tmpMap = new HashMap<>(); + for(final TextDecoration en : TextDecoration.values()) { + tmpMap.put(en.decoration, en); + } + decorationToEnum = ImmutableMap.copyOf(tmpMap); + } + + @Override + public String toString() { + return decoration; + } +} diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 443bb2eee..01d45f6df 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -24,19 +24,6 @@ */ class TextShadowNode extends GroupShadowNode { - // static final String INHERIT = "inherit"; - - // static final String TEXT_ANCHOR_AUTO = "auto"; - // static final String TEXT_ANCHOR_START = "start"; - static final String TEXT_ANCHOR_MIDDLE = "middle"; - static final String TEXT_ANCHOR_END = "end"; - - // static final String TEXT_DECORATION_NONE = "none"; - static final String TEXT_DECORATION_UNDERLINE = "underline"; - // static final String TEXT_DECORATION_OVERLINE = "overline"; - static final String TEXT_DECORATION_LINE_THROUGH = "line-through"; - // static final String TEXT_DECORATION_BLINK = "blink"; - private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; private @Nullable ReadableArray mRotate; From da12d92d5a2976cdce9fbf1965f4eac39c2ec0c0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 20:20:24 +0300 Subject: [PATCH 126/198] Implement TextPathMethod and TextPathSpacing Enums --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 13 +++++++++---- .../main/java/com/horcrux/svg/TextPathMethod.java | 6 ++++++ .../java/com/horcrux/svg/TextPathShadowNode.java | 12 ++++++------ .../main/java/com/horcrux/svg/TextPathSpacing.java | 6 ++++++ 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/TextPathMethod.java create mode 100644 android/src/main/java/com/horcrux/svg/TextPathSpacing.java diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index bc73bc2e1..3a1e8cf5e 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -31,7 +31,6 @@ * Shadow node for virtual TSpan view */ class TSpanShadowNode extends TextShadowNode { - private static final String STRETCH = "stretch"; private static final String FONTS = "fonts/"; private static final String OTF = ".otf"; private static final String TTF = ".ttf"; @@ -117,9 +116,15 @@ private Path getLinePath(String line, Paint paint) { final double size = gc.getFontSize(); final String startOffset = textPath.getStartOffset(); offset += PropHelper.fromRelative(startOffset, distance, 0, mScale, size); - // String spacing = textPath.getSpacing(); // spacing = "auto | exact" - final String method = textPath.getMethod(); // method = "align | stretch" - if (STRETCH.equals(method)) { + /* + TextPathSpacing spacing = textPath.getSpacing(); + if (spacing == TextPathSpacing.auto) { + // Hmm, what to do here? + // https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute + } + */ + final TextPathMethod method = textPath.getMethod(); + if (method == TextPathMethod.stretch) { renderMethodScaling = distance / textMeasure; } } diff --git a/android/src/main/java/com/horcrux/svg/TextPathMethod.java b/android/src/main/java/com/horcrux/svg/TextPathMethod.java new file mode 100644 index 000000000..dc1b82c17 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextPathMethod.java @@ -0,0 +1,6 @@ +package com.horcrux.svg; + +enum TextPathMethod { + align, + stretch +} diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index a5df934d9..9ef406e76 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -23,9 +23,9 @@ class TextPathShadowNode extends TextShadowNode { private String mHref; - private String mMethod; - private String mSpacing; private @Nullable String mStartOffset; + private TextPathMethod mMethod = TextPathMethod.align; + private TextPathSpacing mSpacing = TextPathSpacing.exact; @ReactProp(name = "href") public void setHref(String href) { @@ -41,21 +41,21 @@ public void setStartOffset(@Nullable String startOffset) { @ReactProp(name = "method") public void setMethod(@Nullable String method) { - mMethod = method; + mMethod = TextPathMethod.valueOf(method); markUpdated(); } @ReactProp(name = "spacing") public void setSpacing(@Nullable String spacing) { - mSpacing = spacing; + mSpacing = TextPathSpacing.valueOf(spacing); markUpdated(); } - String getMethod() { + TextPathMethod getMethod() { return mMethod; } - public String getSpacing() { + public TextPathSpacing getSpacing() { return mSpacing; } diff --git a/android/src/main/java/com/horcrux/svg/TextPathSpacing.java b/android/src/main/java/com/horcrux/svg/TextPathSpacing.java new file mode 100644 index 000000000..d1d0d9922 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextPathSpacing.java @@ -0,0 +1,6 @@ +package com.horcrux.svg; + +enum TextPathSpacing { + auto, + exact +} From fb0501936979b954bba1237dd9d35183572dc4f6 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 24 Jul 2017 20:46:19 +0300 Subject: [PATCH 127/198] Fix fontSize propagation and getFontSizeFromContext --- .../main/java/com/horcrux/svg/GlyphContext.java | 1 + .../main/java/com/horcrux/svg/VirtualNode.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 92b5d55a5..424081c2f 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -174,6 +174,7 @@ private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) { } FontData data = new FontData(font, parent, mScale); + mFontSize = data.fontSize; mFontContext.add(data); topFont = data; diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 754989f7d..50f366c86 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -44,7 +44,6 @@ abstract class VirtualNode extends LayoutShadowNode { 0, 0, 1 }; float mOpacity = 1f; - private double mFontSize = -1; Matrix mMatrix = new Matrix(); private int mClipRule; @@ -63,6 +62,7 @@ abstract class VirtualNode extends LayoutShadowNode { private GroupShadowNode mTextRoot; private float canvasHeight = -1; private float canvasWidth = -1; + private GlyphContext glyphContext; VirtualNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; @@ -136,16 +136,16 @@ private GroupShadowNode getTextRoot(Class shadowNodeClass) { } private double getFontSizeFromContext() { - if (mFontSize != -1) { - return mFontSize; - } GroupShadowNode root = getTextRoot(); if (root == null) { - mFontSize = DEFAULT_FONT_SIZE; - } else { - mFontSize = root.getGlyphContext().getFontSize(); + return DEFAULT_FONT_SIZE; } - return mFontSize; + + if (glyphContext == null) { + glyphContext = root.getGlyphContext(); + } + + return glyphContext.getFontSize(); } public abstract void draw(Canvas canvas, Paint paint, float opacity); From a73f50d3f23df06714811ade3bacfab699e87212 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 02:42:58 +0300 Subject: [PATCH 128/198] Implement https://svgwg.org/svg2-draft/text.html#TextpathLayoutRules --- .../java/com/horcrux/svg/TSpanShadowNode.java | 180 +++++++++++++----- 1 file changed, 136 insertions(+), 44 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 3a1e8cf5e..bd5402d4f 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -31,6 +31,9 @@ * Shadow node for virtual TSpan view */ class TSpanShadowNode extends TextShadowNode { + private static final double tau = 2 * Math.PI; + private static final double radToDeg = 360 / tau; + private static final String FONTS = "fonts/"; private static final String OTF = ".otf"; private static final String TTF = ".ttf"; @@ -97,6 +100,29 @@ private Path getLinePath(String line, Paint paint) { double renderMethodScaling = 1; final double textMeasure = paint.measureText(line); + /* + Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ + and property text-anchor. + + For text-anchor:start, startpoint-on-the-path is the point + on the path which represents the point on the path which is ‘startOffset’ distance + along the path from the start of the path, calculated using the user agent's distance + along the path algorithm. + + For text-anchor:middle, startpoint-on-the-path is the point + on the path which represents the point on the path which is [ ‘startOffset’ minus half + of the total advance values for all of the glyphs in the ‘textPath’ element ] distance + along the path from the start of the path, calculated using the user agent's distance + along the path algorithm. + + For text-anchor:end, startpoint-on-the-path is the point on + the path which represents the point on the path which is [ ‘startOffset’ minus the + total advance values for all of the glyphs in the ‘textPath’ element ]. + + Before rendering the first glyph, the horizontal component of the startpoint-on-the-path + is adjusted to take into account various horizontal alignment text properties and + attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. + */ final TextAnchor textAnchor = font.textAnchor; double offset; if (textAnchor == TextAnchor.start) { @@ -113,6 +139,9 @@ private Path getLinePath(String line, Paint paint) { if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); + if (distance == 0) { + return path; + } final double size = gc.getFontSize(); final String startOffset = textPath.getStartOffset(); offset += PropHelper.fromRelative(startOffset, distance, 0, mScale, size); @@ -129,18 +158,8 @@ private Path getLinePath(String line, Paint paint) { } } - double x; - double y; - double r; - double dx; - double dy; - - Path glyph; - Matrix matrix; - String current; - double glyphWidth; String previous = ""; - double previousGlyphWidth = 0; + double previousCharWidth = 0; final char[] chars = line.toCharArray(); /* @@ -171,60 +190,133 @@ private Path getLinePath(String line, Paint paint) { final double letterSpacing = font.letterSpacing; for (int index = 0; index < length; index++) { - glyph = new Path(); - final char currentChar = chars[index]; - current = String.valueOf(currentChar); + Path glyph = new Path(); + char currentChar = chars[index]; + String current = String.valueOf(currentChar); paint.getTextPath(current, 0, 1, 0, 0, glyph); - glyphWidth = paint.measureText(current) * renderMethodScaling; + + /* + Determine the glyph's charwidth (i.e., the amount which the current text position + advances horizontally when the glyph is drawn using horizontal text layout). + */ + double charWidth = paint.measureText(current) * renderMethodScaling; + + /* + For each subsequent glyph, set a new startpoint-on-the-path as the previous + endpoint-on-the-path, but with appropriate adjustments taking into account + horizontal kerning tables in the font and current values of various attributes + and properties, including spacing properties (e.g. letter-spacing and word-spacing) + and ‘tspan’ elements with values provided for attributes ‘dx’ and ‘dy’. All + adjustments are calculated as distance adjustments along the path, calculated + using the user agent's distance along the path algorithm. + */ if (autoKerning) { - double bothGlyphWidth = paint.measureText(previous + current) * renderMethodScaling; - kerning = bothGlyphWidth - previousGlyphWidth - glyphWidth; - previousGlyphWidth = glyphWidth; + double bothCharsWidth = paint.measureText(previous + current) * renderMethodScaling; + kerning = bothCharsWidth - previousCharWidth - charWidth; + previousCharWidth = charWidth; previous = current; } - final boolean isWordSeparator = currentChar == ' '; - final double wordSpace = isWordSeparator ? wordSpacing : 0; - final double advance = glyphWidth + kerning + wordSpace + letterSpacing; + boolean isWordSeparator = currentChar == ' '; + double wordSpace = isWordSeparator ? wordSpacing : 0; + double advance = charWidth + kerning + wordSpace + letterSpacing; - x = gc.nextX(advance); - y = gc.nextY(); - dx = gc.nextDeltaX(); - dy = gc.nextDeltaY(); - r = gc.nextRotation(); + double x = gc.nextX(advance); + double y = gc.nextY(); + double dx = gc.nextDeltaX(); + double dy = gc.nextDeltaY(); + double r = gc.nextRotation(); - matrix = new Matrix(); + Matrix start = new Matrix(); + Matrix mid = new Matrix(); + Matrix end = new Matrix(); - final double glyphStart = offset + x + dx - glyphWidth; + float[] startPointMatrixData = new float[9]; + float[] endPointMatrixData = new float[9]; - if (textPath != null) { - double halfGlyphWidth = glyphWidth / 2; - double midpoint = glyphStart + halfGlyphWidth; + double startpoint = offset + x + dx - charWidth; + if (textPath != null) { + /* + Determine the point on the curve which is charwidth distance along the path from + the startpoint-on-the-path for this glyph, calculated using the user agent's + distance along the path algorithm. This point is the endpoint-on-the-path for + the glyph. + */ + double endpoint = startpoint + charWidth; + + /* + Determine the midpoint-on-the-path, which is the point on the path which is + "halfway" (user agents can choose either a distance calculation or a parametric + calculation) between the startpoint-on-the-path and the endpoint-on-the-path. + */ + double halfway = charWidth / 2; + double midpoint = startpoint + halfway; + + // Glyphs whose midpoint-on-the-path are off the path are not rendered. if (midpoint > distance) { break; } else if (midpoint < 0) { continue; } + /* + Determine the glyph-midline, which is the vertical line in the glyph's + coordinate system that goes through the glyph's x-axis midpoint. + + Position the glyph such that the glyph-midline passes through + the midpoint-on-the-path and is perpendicular to the line + through the startpoint-on-the-path and the endpoint-on-the-path. + */ assert pm != null; - pm.getMatrix((float) midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG); - // TODO In the calculation above, if either the startpoint-on-the-path - // or the endpoint-on-the-path is off the end of the path, - // then extend the path beyond its end points with a straight line - // that is parallel to the tangent at the path at its end point - // so that the midpoint-on-the-path can still be calculated. - - matrix.preTranslate((float) -halfGlyphWidth, (float) dy); - matrix.preScale((float) renderMethodScaling, (float) renderMethodScaling); - matrix.postTranslate(0, (float) y); + if (startpoint < 0 || endpoint > distance) { + /* + In the calculation above, if either the startpoint-on-the-path + or the endpoint-on-the-path is off the end of the path, + TODO then extend the path beyond its end points with a straight line + that is parallel to the tangent at the path at its end point + so that the midpoint-on-the-path can still be calculated. + + TODO suggest change in wording of svg spec: + so that the midpoint-on-the-path can still be calculated + to + so that the angle of the glyph-midline to the x-axis can still be calculated + */ + final int flags = POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG; + pm.getMatrix((float) midpoint, mid, flags); + } else { + pm.getMatrix((float) startpoint, start, POSITION_MATRIX_FLAG); + pm.getMatrix((float) midpoint, mid, POSITION_MATRIX_FLAG); + pm.getMatrix((float) endpoint, end, POSITION_MATRIX_FLAG); + + start.getValues(startPointMatrixData); + end.getValues(endPointMatrixData); + + double startX = startPointMatrixData[2]; + double startY = startPointMatrixData[5]; + double endX = endPointMatrixData[2]; + double endY = endPointMatrixData[5]; + + double glyphMidlineAngle = Math.atan2(endY - startY, endX - startX) * radToDeg; + + mid.preRotate((float) glyphMidlineAngle); + } + + /* + TODO alignment-baseline + Align the glyph vertically relative to the midpoint-on-the-path based on property + alignment-baseline and any specified values for attribute ‘dy’ on a ‘tspan’ element. + */ + mid.preTranslate((float) -halfway, (float) dy); + mid.preScale((float) renderMethodScaling, (float) renderMethodScaling); + mid.postTranslate(0, (float) y); } else { - matrix.setTranslate((float) glyphStart, (float) (y + dy)); + mid.setTranslate((float) startpoint, (float) (y + dy)); } - matrix.preRotate((float) r); - glyph.transform(matrix); + mid.preRotate((float) r); + glyph.transform(mid); path.addPath(glyph); } From 4cfdfd9bf255a8d5d44d0f9c2c65d1d0d4e22d9a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 03:08:19 +0300 Subject: [PATCH 129/198] Restructure --- .../java/com/horcrux/svg/TSpanShadowNode.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index bd5402d4f..50a6b4d1c 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -96,10 +96,6 @@ private Path getLinePath(String line, Paint paint) { FontData font = gc.getFont(); applyTextPropertiesToPaint(paint, font); - double distance = 0; - double renderMethodScaling = 1; - final double textMeasure = paint.measureText(line); - /* Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ and property text-anchor. @@ -123,8 +119,9 @@ private Path getLinePath(String line, Paint paint) { is adjusted to take into account various horizontal alignment text properties and attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. */ - final TextAnchor textAnchor = font.textAnchor; double offset; + final TextAnchor textAnchor = font.textAnchor; + final double textMeasure = paint.measureText(line); if (textAnchor == TextAnchor.start) { offset = 0; } else { @@ -135,7 +132,9 @@ private Path getLinePath(String line, Paint paint) { } } + double distance = 0; PathMeasure pm = null; + double renderMethodScaling = 1; if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); @@ -158,10 +157,6 @@ private Path getLinePath(String line, Paint paint) { } } - String previous = ""; - double previousCharWidth = 0; - final char[] chars = line.toCharArray(); - /* * * Three properties affect the space between characters and words: @@ -181,19 +176,17 @@ private Path getLinePath(String line, Paint paint) { * or decrease the space between typographic character units in order to justify text. * * */ - - final boolean autoKerning = !font.manualKerning; + String previous = ""; + double previousCharWidth = 0; double kerning = font.kerning; - final double wordSpacing = font.wordSpacing; - final double letterSpacing = font.letterSpacing; + final boolean autoKerning = !font.manualKerning; + final char[] chars = line.toCharArray(); for (int index = 0; index < length; index++) { - Path glyph = new Path(); char currentChar = chars[index]; String current = String.valueOf(currentChar); - paint.getTextPath(current, 0, 1, 0, 0, glyph); /* Determine the glyph's charwidth (i.e., the amount which the current text position @@ -316,6 +309,9 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci } mid.preRotate((float) r); + + Path glyph = new Path(); + paint.getTextPath(current, 0, 1, 0, 0, glyph); glyph.transform(mid); path.addPath(glyph); } @@ -329,7 +325,6 @@ private void applyTextPropertiesToPaint(Paint paint, FontData font) { double fontSize = font.fontSize * mScale; boolean isBold = font.fontWeight == FontWeight.Bold; - boolean isItalic = font.fontStyle == FontStyle.italic; boolean underlineText = false; From fddb4008d1a4c3ba2fcfbe7d47e5b4e2516e57d4 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 04:07:04 +0300 Subject: [PATCH 130/198] Extend and improve propTypes. Optimize javascript hidden classes and inline caching in V8. Add lengthAdjust and textLength text / tspan attributes. --- elements/TSpan.js | 9 +--- elements/Text.js | 9 +--- elements/TextPath.js | 11 +--- lib/attributes.js | 39 ++++++++------ lib/props.js | 118 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 137 insertions(+), 49 deletions(-) diff --git a/elements/TSpan.js b/elements/TSpan.js index 540291f6c..37b0d7cba 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shims/createReactNativeComponentClass'; import extractText from '../lib/extract/extractText'; -import {pathProps, fontProps} from '../lib/props'; +import {textProps} from '../lib/props'; import {TSpanAttibutes} from '../lib/attributes'; import extractProps from '../lib/extract/extractProps'; import Shape from './Shape'; @@ -11,12 +11,7 @@ import Shape from './Shape'; export default class extends Shape { static displayName = 'TSpan'; - static propTypes = { - ...pathProps, - ...fontProps, - dx: PropTypes.string, - dy: PropTypes.string, - }; + static propTypes = textProps; //noinspection JSUnusedGlobalSymbols static childContextTypes = { diff --git a/elements/Text.js b/elements/Text.js index d8d68b290..b86cba680 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shims/createReactNativeComponentClass'; import extractText from '../lib/extract/extractText'; -import {pathProps, fontProps} from '../lib/props'; +import {textProps} from '../lib/props'; import {TextAttributes} from '../lib/attributes'; import extractProps from '../lib/extract/extractProps'; import Shape from './Shape'; @@ -10,12 +10,7 @@ import Shape from './Shape'; export default class extends Shape { static displayName = 'Text'; - static propTypes = { - ...pathProps, - ...fontProps, - dx: PropTypes.string, - dy: PropTypes.string, - }; + static propTypes = textProps; //noinspection JSUnusedGlobalSymbols static childContextTypes = { diff --git a/elements/TextPath.js b/elements/TextPath.js index c715f11e6..5656252f5 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -4,7 +4,7 @@ import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shi import {TextPathAttributes} from '../lib/attributes'; import extractText from '../lib/extract/extractText'; import Shape from './Shape'; -import {pathProps, fontProps} from '../lib/props'; +import {textPathProps} from '../lib/props'; import extractProps from '../lib/extract/extractProps'; import TSpan from './TSpan'; @@ -13,14 +13,7 @@ const idExpReg = /^#(.+)$/; export default class extends Shape { static displayName = 'Span'; - static propTypes = { - ...pathProps, - ...fontProps, - href: PropTypes.string.isRequired, - method: PropTypes.oneOf(['align', 'stretch']), - spacing: PropTypes.oneOf(['auto', 'exact']), - startOffset: PropTypes.string - }; + static propTypes = textPathProps; render() { let {children, href, startOffset, ...props} = this.props; diff --git a/lib/attributes.js b/lib/attributes.js index 59c1fe197..f1b17c81a 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -85,30 +85,37 @@ const RenderableAttributes = { }; const GroupAttributes = { + ...RenderableAttributes, font: { diff: fontDiffer }, - ...RenderableAttributes }; const UseAttributes = { + ...RenderableAttributes, href: true, width: true, height: true, - ...RenderableAttributes }; const SymbolAttributes = { + ...ViewBoxAttributes, name: true, - ...ViewBoxAttributes }; const PathAttributes = { + ...RenderableAttributes, d: true, - ...RenderableAttributes +}; + +const TextSpecificAttributes = { + ...RenderableAttributes, + lengthAdjust: true, + textLength: true, }; const TextAttributes = { + ...TextSpecificAttributes, font: { diff: fontDiffer }, @@ -117,20 +124,20 @@ const TextAttributes = { rotate: arrayDiffer, positionX: arrayDiffer, positionY: arrayDiffer, - ...RenderableAttributes }; const TextPathAttributes = { + ...TextSpecificAttributes, href: true, + startOffset: true, method: true, spacing: true, - startOffset: true, - ...RenderableAttributes + side: true, }; const TSpanAttibutes = { + ...TextAttributes, content: true, - ...TextAttributes }; const ClipPathAttributes = { @@ -138,6 +145,7 @@ const ClipPathAttributes = { }; const GradientAttributes = { + ...ClipPathAttributes, gradient: { diff: arrayDiffer }, @@ -145,18 +153,18 @@ const GradientAttributes = { gradientTransform: { diff: arrayDiffer }, - ...ClipPathAttributes }; const LinearGradientAttributes = { + ...GradientAttributes, x1: true, y1: true, x2: true, y2: true, - ...GradientAttributes }; const RadialGradientAttributes = { + ...GradientAttributes, fx: true, fy: true, rx: true, @@ -164,26 +172,26 @@ const RadialGradientAttributes = { cx: true, cy: true, r: true, - ...GradientAttributes }; const CircleAttributes = { + ...RenderableAttributes, cx: true, cy: true, r: true, - ...RenderableAttributes }; const EllipseAttributes = { + ...RenderableAttributes, cx: true, cy: true, rx: true, ry: true, - ...RenderableAttributes }; const ImageAttributes = { + ...RenderableAttributes, x: true, y: true, width: true, @@ -191,25 +199,24 @@ const ImageAttributes = { src: true, align: true, meetOrSlice: true, - ...RenderableAttributes }; const LineAttributes = { + ...RenderableAttributes, x1: true, y1: true, x2: true, y2: true, - ...RenderableAttributes }; const RectAttributes = { + ...RenderableAttributes, x: true, y: true, width: true, height: true, rx: true, ry: true, - ...RenderableAttributes }; export { diff --git a/lib/props.js b/lib/props.js index b3d39a00a..29561c2ec 100644 --- a/lib/props.js +++ b/lib/props.js @@ -48,6 +48,16 @@ const strokeProps = { strokeMiterlimit: numberProp }; +const pathProps = { + ...fillProps, + ...strokeProps, + ...clipProps, + ...transformProps, + ...responderProps, + ...touchableProps, + ...definationProps +}; + // normal | italic | oblique | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style const fontStyle = PropTypes.oneOf(['normal', 'italic', 'oblique']); @@ -127,6 +137,102 @@ const fontProps = { font }; +/* + Name Value Initial value Animatable + lengthAdjust spacing | spacingAndGlyphs spacing yes + https://svgwg.org/svg2-draft/text.html#TextElementLengthAdjustAttribute + https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust + */ +const lengthAdjust = PropTypes.oneOf(['spacing', 'spacingAndGlyphs']); + +/* + Name Value Initial value Animatable + textLength | | See below yes + https://svgwg.org/svg2-draft/text.html#TextElementTextLengthAttribute + https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength +*/ +const textLength = PropTypes.string; + +const textSpecificProps = { + ...pathProps, + ...fontProps, + lengthAdjust, + textLength, +} + +// https://svgwg.org/svg2-draft/text.html#TSpanAttributes +const textProps = { + ...textSpecificProps, + dx: PropTypes.string, + dy: PropTypes.string, +} + +/* + Name + side + Value + left | right + initial value + left + Animatable + yes + https://svgwg.org/svg2-draft/text.html#TextPathElementSideAttribute +*/ +const side = PropTypes.oneOf(['left', 'right']); + +/* + Name + startOffset + Value + | | + initial value + 0 + Animatable + yes + https://svgwg.org/svg2-draft/text.html#TextPathElementStartOffsetAttribute + https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath + */ +const startOffset = PropTypes.string; + +/* + Name + method + Value + align | stretch + initial value + align + Animatable + yes + https://svgwg.org/svg2-draft/text.html#TextPathElementMethodAttribute + https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath + */ +const method = PropTypes.oneOf(['align', 'stretch']); + +/* + Name + spacing + Value + auto | exact + initial value + exact + Animatable + yes + https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute + https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath + */ +const spacing = PropTypes.oneOf(['auto', 'exact']); + +// https://svgwg.org/svg2-draft/text.html#TextPathAttributes +// https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath +const textPathProps = { + ...textSpecificProps, + href: PropTypes.string.isRequired, + startOffset, + method, + spacing, + side, +} + const transformProps = { scale: numberProp, scaleX: numberProp, @@ -147,21 +253,13 @@ const transformProps = { transform: PropTypes.oneOfType([PropTypes.object, PropTypes.string]) }; -const pathProps = { - ...fillProps, - ...strokeProps, - ...clipProps, - ...transformProps, - ...responderProps, - ...touchableProps, - ...definationProps -}; - export { numberProp, fillProps, strokeProps, fontProps, + textProps, + textPathProps, clipProps, pathProps, responderProps, From 957174e3e7a7fe79596fd2f6c12778ef81cf7db2 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 05:35:48 +0300 Subject: [PATCH 131/198] Begin alignmentBaseline implementation --- .../com/horcrux/svg/AlignmentBaseline.java | 63 +++++++++++++++++++ .../main/java/com/horcrux/svg/FontData.java | 4 ++ .../java/com/horcrux/svg/TSpanShadowNode.java | 32 ++++++++++ 3 files changed, 99 insertions(+) create mode 100644 android/src/main/java/com/horcrux/svg/AlignmentBaseline.java diff --git a/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java b/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java new file mode 100644 index 000000000..0dc6e1b99 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java @@ -0,0 +1,63 @@ +package com.horcrux.svg; + +import com.facebook.common.internal.ImmutableMap; + +import java.util.HashMap; +import java.util.Map; + +/* + https://drafts.csswg.org/css-inline/#propdef-alignment-baseline + 2.2.1. Alignment Point: alignment-baseline longhand + + Name: alignment-baseline + Value: baseline | text-bottom | alphabetic | ideographic | middle | central | mathematical | text-top | bottom | center | top + Initial: baseline + Applies to: inline-level boxes, flex items, grid items, table cells + Inherited: no + Percentages: N/A + Media: visual + Computed value: as specified + Canonical order: per grammar + Animation type: discrete +*/ +enum AlignmentBaseline { + baseline("baseline"), + textBottom("text-bottom"), + alphabetic("alphabetic"), + ideographic("ideographic"), + middle("middle"), + central("central"), + mathematical("mathematical"), + textTop("text-top"), + bottom("bottom"), + center("center"), + top("top"); + + private final String weight; + + AlignmentBaseline(String weight) { + this.weight = weight; + } + + public static AlignmentBaseline getEnum(String strVal) { + if (!weightToEnum.containsKey(strVal)) { + throw new IllegalArgumentException("Unknown String Value: " + strVal); + } + return weightToEnum.get(strVal); + } + + private static final Map weightToEnum; + + static { + final Map tmpMap = new HashMap<>(); + for (final AlignmentBaseline en : AlignmentBaseline.values()) { + tmpMap.put(en.weight, en); + } + weightToEnum = ImmutableMap.copyOf(tmpMap); + } + + @Override + public String toString() { + return weight; + } +} diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index 7c10bb117..f78efefab 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -19,6 +19,7 @@ class FontData { private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; private static final String TEXT_DECORATION = "textDecoration"; + private static final String ALIGNMENT_BASELINE = "alignment-baseline"; final double fontSize; @@ -28,6 +29,7 @@ class FontData { final TextAnchor textAnchor; final TextDecoration textDecoration; + final AlignmentBaseline alignmentBaseline; final double kerning; final double wordSpacing; @@ -44,6 +46,7 @@ private FontData() { textAnchor = TextAnchor.start; textDecoration = TextDecoration.None; + alignmentBaseline = AlignmentBaseline.baseline; manualKerning = false; kerning = DEFAULT_KERNING; @@ -84,6 +87,7 @@ private double toAbsolute(String string, double scale, double fontSize) { textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor; textDecoration = font.hasKey(TEXT_DECORATION) ? TextDecoration.getEnum(font.getString(TEXT_DECORATION)) : parent.textDecoration; + alignmentBaseline = font.hasKey(ALIGNMENT_BASELINE) ? AlignmentBaseline.getEnum(font.getString(ALIGNMENT_BASELINE)) : parent.alignmentBaseline; final boolean hasKerning = font.hasKey(KERNING); manualKerning = hasKerning || parent.manualKerning; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 50a6b4d1c..97e5ef7f1 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -183,6 +183,13 @@ private Path getLinePath(String line, Paint paint) { final double letterSpacing = font.letterSpacing; final boolean autoKerning = !font.manualKerning; + Paint.FontMetrics fm = paint.getFontMetrics(); + float ascent = fm.ascent; + float bottom = fm.bottom; + float descent = fm.descent; + float leading = fm.leading; + float top = fm.top; + final char[] chars = line.toCharArray(); for (int index = 0; index < length; index++) { char currentChar = chars[index]; @@ -300,7 +307,32 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci TODO alignment-baseline Align the glyph vertically relative to the midpoint-on-the-path based on property alignment-baseline and any specified values for attribute ‘dy’ on a ‘tspan’ element. + + https://developer.mozilla.org/en/docs/Web/CSS/vertical-align + https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html + https://www.microsoft.com/typography/otspec/base.htm + http://apike.ca/prog_svg_text_style.html + https://www.w3schools.com/tags/canvas_textbaseline.asp + http://vanseodesign.com/web-design/svg-text-baseline-alignment/ + https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align + https://tympanus.net/codrops/css_reference/vertical-align/ */ + switch (font.alignmentBaseline) { + case baseline: + case textBottom: + case alphabetic: + case ideographic: + case middle: + case central: + case mathematical: + case textTop: + case bottom: + case center: + case top: + default: + } + mid.preTranslate((float) -halfway, (float) dy); mid.preScale((float) renderMethodScaling, (float) renderMethodScaling); mid.postTranslate(0, (float) y); From 7cafe835dfca28b46544d3bf34dac5f0d2091c6c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 07:04:12 +0300 Subject: [PATCH 132/198] First alignmentBaseline implementation attempt --- .../java/com/horcrux/svg/TSpanShadowNode.java | 96 +++++++++++++++++-- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 97e5ef7f1..748828110 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -16,6 +16,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; @@ -184,11 +185,11 @@ private Path getLinePath(String line, Paint paint) { final boolean autoKerning = !font.manualKerning; Paint.FontMetrics fm = paint.getFontMetrics(); - float ascent = fm.ascent; - float bottom = fm.bottom; - float descent = fm.descent; - float leading = fm.leading; - float top = fm.top; + double top = fm.top; + double bottom = fm.bottom; + double ascent = fm.ascent; + double descent = fm.descent; + double totalHeight = top - bottom; final char[] chars = line.toCharArray(); for (int index = 0; index < length; index++) { @@ -309,7 +310,6 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci alignment-baseline and any specified values for attribute ‘dy’ on a ‘tspan’ element. https://developer.mozilla.org/en/docs/Web/CSS/vertical-align - https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html https://www.microsoft.com/typography/otspec/base.htm http://apike.ca/prog_svg_text_style.html @@ -317,23 +317,103 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci http://vanseodesign.com/web-design/svg-text-baseline-alignment/ https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align https://tympanus.net/codrops/css_reference/vertical-align/ + + 11.10.2.6. The ‘alignment-baseline’ property + + This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3] + + The vertical-align property shorthand should be preferred in new content. + + SVG 2 introduces some changes to the definition of this property. + In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed. + For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and + 'text-after-edge' should be mapped to 'text-bottom'. + + Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. + + SVG implementations may support the following aliases in order to support legacy content: + TODO? + text-before-edge = text-top + text-after-edge = text-bottom */ + double baselineShift; switch (font.alignmentBaseline) { + // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + default: case baseline: + // Use the dominant baseline choice of the parent. + // Match the box’s corresponding baseline to that of its parent. + baselineShift = 0; + break; + case textBottom: + // Match the bottom of the box to the bottom of the parent’s content area. + // text-after-edge = text-bottom + // text-after-edge = descender depth + baselineShift = descent; + break; + case alphabetic: + // Match the box’s alphabetic baseline to that of its parent. + // alphabetic = 0 + baselineShift = 0; + break; + case ideographic: + // Match the box’s ideographic character face under-side baseline to that of its parent. + // ideographic = descender depth + baselineShift = descent; + break; + case middle: + // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. + // middle = x height / 2 + Rect bounds = new Rect(); + // this will just retrieve the bounding rect for 'x' + paint.getTextBounds("x", 0, 1, bounds); + int xHeight = bounds.height(); + baselineShift = xHeight / 2; + break; + case central: + // Match the box’s central baseline to the central baseline of its parent. + // central = (ascender height - descender depth) / 2 + baselineShift = (ascent - descent) / 2; + break; + case mathematical: + // Match the box’s mathematical baseline to that of its parent. + // Hanging and mathematical baselines + // There are no obvious formulas to calculate the position of these baselines. + // At the time of writing FOP puts the hanging baseline at 80% of the ascender + // height and the mathematical baseline at 50%. + baselineShift = ascent / 2; + break; + case textTop: + // Match the top of the box to the top of the parent’s content area. + // text-before-edge = text-top + // text-before-edge = ascender height + baselineShift = ascent; + break; + case bottom: + // Align the top of the aligned subtree with the top of the line box. + baselineShift = bottom; + break; + case center: + // Align the center of the aligned subtree with the center of the line box. + baselineShift = totalHeight / 2; + break; + case top: - default: + // Align the bottom of the aligned subtree with the bottom of the line box. + baselineShift = top; + break; } - mid.preTranslate((float) -halfway, (float) dy); + mid.preTranslate((float) -halfway, (float) (dy + baselineShift)); mid.preScale((float) renderMethodScaling, (float) renderMethodScaling); mid.postTranslate(0, (float) y); } else { From c05c21549677aa879131a1924782a5def0446c06 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 07:45:08 +0300 Subject: [PATCH 133/198] Implement prop extraction for side and alignmentBaseline. Cleanup extractText, fix linting. --- .../com/horcrux/svg/AlignmentBaseline.java | 18 +++--- .../main/java/com/horcrux/svg/FontData.java | 2 +- elements/TextPath.js | 8 +-- lib/attributes.js | 1 + lib/extract/extractText.js | 11 +--- lib/props.js | 62 ++++++++++++------- 6 files changed, 57 insertions(+), 45 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java b/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java index 0dc6e1b99..4cc73f999 100644 --- a/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java +++ b/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java @@ -33,31 +33,31 @@ enum AlignmentBaseline { center("center"), top("top"); - private final String weight; + private final String alignment; - AlignmentBaseline(String weight) { - this.weight = weight; + AlignmentBaseline(String alignment) { + this.alignment = alignment; } public static AlignmentBaseline getEnum(String strVal) { - if (!weightToEnum.containsKey(strVal)) { + if (!alignmentToEnum.containsKey(strVal)) { throw new IllegalArgumentException("Unknown String Value: " + strVal); } - return weightToEnum.get(strVal); + return alignmentToEnum.get(strVal); } - private static final Map weightToEnum; + private static final Map alignmentToEnum; static { final Map tmpMap = new HashMap<>(); for (final AlignmentBaseline en : AlignmentBaseline.values()) { - tmpMap.put(en.weight, en); + tmpMap.put(en.alignment, en); } - weightToEnum = ImmutableMap.copyOf(tmpMap); + alignmentToEnum = ImmutableMap.copyOf(tmpMap); } @Override public String toString() { - return weight; + return alignment; } } diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index f78efefab..71e9ea73a 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -19,7 +19,7 @@ class FontData { private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; private static final String TEXT_DECORATION = "textDecoration"; - private static final String ALIGNMENT_BASELINE = "alignment-baseline"; + private static final String ALIGNMENT_BASELINE = "alignmentBaseline"; final double fontSize; diff --git a/elements/TextPath.js b/elements/TextPath.js index 5656252f5..24627f234 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shims/createReactNativeComponentClass'; import {TextPathAttributes} from '../lib/attributes'; import extractText from '../lib/extract/extractText'; @@ -16,15 +15,15 @@ export default class extends Shape { static propTypes = textPathProps; render() { - let {children, href, startOffset, ...props} = this.props; + let {children, href, startOffset, method, spacing, side, ...props} = this.props; if (href) { let matched = href.match(idExpReg); if (matched) { href = matched[1]; - + startOffset = `${startOffset || 0}`; return ; } diff --git a/lib/attributes.js b/lib/attributes.js index f1b17c81a..8854e66b9 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -110,6 +110,7 @@ const PathAttributes = { const TextSpecificAttributes = { ...RenderableAttributes, + alignmentBaseline: true, lengthAdjust: true, textLength: true, }; diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 11b9be37c..f685a04d5 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -51,6 +51,7 @@ export function extractFont(props) { fontStretch, textAnchor, textDecoration, + alignmentBaseline, letterSpacing, wordSpacing, kerning, @@ -73,6 +74,7 @@ export function extractFont(props) { fontFamily, textAnchor, textDecoration, + alignmentBaseline, letterSpacing, wordSpacing, kerning, @@ -103,13 +105,10 @@ export default function(props, container) { y, dx, dy, - method, - spacing, } = props; let { rotate, - children, - startOffset + children } = props; const positionX = parseSVGLengthList(x); @@ -138,7 +137,6 @@ export default function(props, container) { } const font = extractFont(props); - startOffset = (startOffset || 0).toString(); return { font, @@ -149,8 +147,5 @@ export default function(props, container) { rotate, deltaX, deltaY, - method, - spacing, - startOffset, }; } diff --git a/lib/props.js b/lib/props.js index 29561c2ec..585910417 100644 --- a/lib/props.js +++ b/lib/props.js @@ -48,6 +48,26 @@ const strokeProps = { strokeMiterlimit: numberProp }; +const transformProps = { + scale: numberProp, + scaleX: numberProp, + scaleY: numberProp, + rotate: numberProp, + rotation: numberProp, + translate: numberProp, + translateX: numberProp, + translateY: numberProp, + x: numberProp, + y: numberProp, + origin: numberProp, + originX: numberProp, + originY: numberProp, + skew: numberProp, + skewX: numberProp, + skewY: numberProp, + transform: PropTypes.oneOfType([PropTypes.object, PropTypes.string]) +}; + const pathProps = { ...fillProps, ...strokeProps, @@ -153,19 +173,37 @@ const lengthAdjust = PropTypes.oneOf(['spacing', 'spacingAndGlyphs']); */ const textLength = PropTypes.string; +/* + Name: alignment-baseline + Value: baseline | text-bottom | alphabetic | ideographic | middle | central | mathematical | text-top | bottom | center | top + Initial: baseline + Applies to: inline-level boxes, flex items, grid items, table cells + Inherited: no + Percentages: N/A + Media: visual + Computed value: as specified + Canonical order: per grammar + Animation type: discrete + https://drafts.csswg.org/css-inline/#propdef-alignment-baseline + https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty + https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline +*/ +const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabetic', 'ideographic', 'middle', 'central', 'mathematical', 'text-top', 'bottom', 'center', 'top']); + const textSpecificProps = { ...pathProps, ...fontProps, + alignmentBaseline, lengthAdjust, textLength, -} +}; // https://svgwg.org/svg2-draft/text.html#TSpanAttributes const textProps = { ...textSpecificProps, dx: PropTypes.string, dy: PropTypes.string, -} +}; /* Name @@ -231,26 +269,6 @@ const textPathProps = { method, spacing, side, -} - -const transformProps = { - scale: numberProp, - scaleX: numberProp, - scaleY: numberProp, - rotate: numberProp, - rotation: numberProp, - translate: numberProp, - translateX: numberProp, - translateY: numberProp, - x: numberProp, - y: numberProp, - origin: numberProp, - originX: numberProp, - originY: numberProp, - skew: numberProp, - skewX: numberProp, - skewY: numberProp, - transform: PropTypes.oneOfType([PropTypes.object, PropTypes.string]) }; export { From b140d673d71cc32bf2b3028bac87813d20bf2634 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 08:22:56 +0300 Subject: [PATCH 134/198] Fix interpretation of alignmentBaseline (Shouldn't inherit). --- .../main/java/com/horcrux/svg/FontData.java | 3 - .../java/com/horcrux/svg/TSpanShadowNode.java | 154 +++++++++--------- .../com/horcrux/svg/TextPathShadowNode.java | 11 ++ .../java/com/horcrux/svg/TextShadowNode.java | 7 + elements/TextPath.js | 6 +- lib/extract/extractText.js | 4 +- 6 files changed, 101 insertions(+), 84 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index 71e9ea73a..c8e643034 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -29,7 +29,6 @@ class FontData { final TextAnchor textAnchor; final TextDecoration textDecoration; - final AlignmentBaseline alignmentBaseline; final double kerning; final double wordSpacing; @@ -46,7 +45,6 @@ private FontData() { textAnchor = TextAnchor.start; textDecoration = TextDecoration.None; - alignmentBaseline = AlignmentBaseline.baseline; manualKerning = false; kerning = DEFAULT_KERNING; @@ -87,7 +85,6 @@ private double toAbsolute(String string, double scale, double fontSize) { textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor; textDecoration = font.hasKey(TEXT_DECORATION) ? TextDecoration.getEnum(font.getString(TEXT_DECORATION)) : parent.textDecoration; - alignmentBaseline = font.hasKey(ALIGNMENT_BASELINE) ? AlignmentBaseline.getEnum(font.getString(ALIGNMENT_BASELINE)) : parent.alignmentBaseline; final boolean hasKerning = font.hasKey(KERNING); manualKerning = hasKerning || parent.manualKerning; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 748828110..7e1bd10fa 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -336,84 +336,86 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci text-before-edge = text-top text-after-edge = text-bottom */ - double baselineShift; - switch (font.alignmentBaseline) { - // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling - default: - case baseline: - // Use the dominant baseline choice of the parent. - // Match the box’s corresponding baseline to that of its parent. - baselineShift = 0; - break; - - case textBottom: - // Match the bottom of the box to the bottom of the parent’s content area. - // text-after-edge = text-bottom - // text-after-edge = descender depth - baselineShift = descent; - break; - - case alphabetic: - // Match the box’s alphabetic baseline to that of its parent. - // alphabetic = 0 - baselineShift = 0; - break; - - case ideographic: - // Match the box’s ideographic character face under-side baseline to that of its parent. - // ideographic = descender depth - baselineShift = descent; - break; - - case middle: - // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. - // middle = x height / 2 - Rect bounds = new Rect(); - // this will just retrieve the bounding rect for 'x' - paint.getTextBounds("x", 0, 1, bounds); - int xHeight = bounds.height(); - baselineShift = xHeight / 2; - break; - - case central: - // Match the box’s central baseline to the central baseline of its parent. - // central = (ascender height - descender depth) / 2 - baselineShift = (ascent - descent) / 2; - break; - - case mathematical: - // Match the box’s mathematical baseline to that of its parent. - // Hanging and mathematical baselines - // There are no obvious formulas to calculate the position of these baselines. - // At the time of writing FOP puts the hanging baseline at 80% of the ascender - // height and the mathematical baseline at 50%. - baselineShift = ascent / 2; - break; - - case textTop: - // Match the top of the box to the top of the parent’s content area. - // text-before-edge = text-top - // text-before-edge = ascender height - baselineShift = ascent; - break; - - case bottom: - // Align the top of the aligned subtree with the top of the line box. - baselineShift = bottom; - break; - - case center: - // Align the center of the aligned subtree with the center of the line box. - baselineShift = totalHeight / 2; - break; - - case top: - // Align the bottom of the aligned subtree with the bottom of the line box. - baselineShift = top; - break; + double baselineShift = 0; + if (mAlignmentBaseline != null) { + switch (mAlignmentBaseline) { + // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + default: + case baseline: + // Use the dominant baseline choice of the parent. + // Match the box’s corresponding baseline to that of its parent. + baselineShift = 0; + break; + + case textBottom: + // Match the bottom of the box to the bottom of the parent’s content area. + // text-after-edge = text-bottom + // text-after-edge = descender depth + baselineShift = descent; + break; + + case alphabetic: + // Match the box’s alphabetic baseline to that of its parent. + // alphabetic = 0 + baselineShift = 0; + break; + + case ideographic: + // Match the box’s ideographic character face under-side baseline to that of its parent. + // ideographic = descender depth + baselineShift = descent; + break; + + case middle: + // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. + // middle = x height / 2 + Rect bounds = new Rect(); + // this will just retrieve the bounding rect for 'x' + paint.getTextBounds("x", 0, 1, bounds); + int xHeight = bounds.height(); + baselineShift = xHeight / 2; + break; + + case central: + // Match the box’s central baseline to the central baseline of its parent. + // central = (ascender height - descender depth) / 2 + baselineShift = (ascent - descent) / 2; + break; + + case mathematical: + // Match the box’s mathematical baseline to that of its parent. + // Hanging and mathematical baselines + // There are no obvious formulas to calculate the position of these baselines. + // At the time of writing FOP puts the hanging baseline at 80% of the ascender + // height and the mathematical baseline at 50%. + baselineShift = ascent / 2; + break; + + case textTop: + // Match the top of the box to the top of the parent’s content area. + // text-before-edge = text-top + // text-before-edge = ascender height + baselineShift = ascent; + break; + + case bottom: + // Align the top of the aligned subtree with the top of the line box. + baselineShift = bottom; + break; + + case center: + // Align the center of the aligned subtree with the center of the line box. + baselineShift = totalHeight / 2; + break; + + case top: + // Align the bottom of the aligned subtree with the bottom of the line box. + baselineShift = top; + break; + } } - mid.preTranslate((float) -halfway, (float) (dy + baselineShift)); + mid.preTranslate((float) -halfway, (float) (dy - baselineShift)); mid.preScale((float) renderMethodScaling, (float) renderMethodScaling); mid.postTranslate(0, (float) y); } else { diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index 9ef406e76..6223e1f77 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -26,6 +26,7 @@ class TextPathShadowNode extends TextShadowNode { private @Nullable String mStartOffset; private TextPathMethod mMethod = TextPathMethod.align; private TextPathSpacing mSpacing = TextPathSpacing.exact; + private TextPathSide mSide; @ReactProp(name = "href") public void setHref(String href) { @@ -51,6 +52,12 @@ public void setSpacing(@Nullable String spacing) { markUpdated(); } + @ReactProp(name = "side") + public void setSide(@Nullable String side) { + mSide = TextPathSide.valueOf(side); + markUpdated(); + } + TextPathMethod getMethod() { return mMethod; } @@ -59,6 +66,10 @@ public TextPathSpacing getSpacing() { return mSpacing; } + public TextPathSide getSide() { + return mSide; + } + String getStartOffset() { return mStartOffset; } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 01d45f6df..dd9d7c775 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -24,12 +24,19 @@ */ class TextShadowNode extends GroupShadowNode { + AlignmentBaseline mAlignmentBaseline; private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; private @Nullable ReadableArray mRotate; private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaY; + @ReactProp(name = "alignmentBaseline") + public void setMethod(@Nullable String alignment) { + mAlignmentBaseline = AlignmentBaseline.valueOf(alignment); + markUpdated(); + } + @ReactProp(name = "rotate") public void setRotate(@Nullable ReadableArray rotate) { mRotate = rotate; diff --git a/elements/TextPath.js b/elements/TextPath.js index 24627f234..a286ac1d1 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -15,7 +15,7 @@ export default class extends Shape { static propTypes = textPathProps; render() { - let {children, href, startOffset, method, spacing, side, ...props} = this.props; + let {children, href, startOffset, method, spacing, side, alignmentBaseline, ...props} = this.props; if (href) { let matched = href.match(idExpReg); @@ -23,11 +23,11 @@ export default class extends Shape { href = matched[1]; startOffset = `${startOffset || 0}`; return Date: Tue, 25 Jul 2017 10:00:32 +0300 Subject: [PATCH 135/198] Add alignment-baseline aliases to support legacy content. --- .../com/horcrux/svg/AlignmentBaseline.java | 15 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 277 ++++++++++-------- lib/props.js | 7 +- 3 files changed, 166 insertions(+), 133 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java b/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java index 4cc73f999..abfdc8112 100644 --- a/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java +++ b/android/src/main/java/com/horcrux/svg/AlignmentBaseline.java @@ -31,7 +31,20 @@ enum AlignmentBaseline { textTop("text-top"), bottom("bottom"), center("center"), - top("top"); + top("top"), + /* + SVG implementations may support the following aliases in order to support legacy content: + + text-before-edge = text-top + text-after-edge = text-bottom + */ + textBeforeEdge("text-before-edge"), + textAfterEdge("text-after-edge"), + // SVG 1.1 + beforeEdge("before-edge"), + afterEdge("after-edge"), + hanging("hanging"), + ; private final String alignment; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 7e1bd10fa..858211038 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -25,6 +25,8 @@ import javax.annotation.Nullable; +import static android.graphics.Matrix.MTRANS_X; +import static android.graphics.Matrix.MTRANS_Y; import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG; import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG; @@ -184,12 +186,124 @@ private Path getLinePath(String line, Paint paint) { final double letterSpacing = font.letterSpacing; final boolean autoKerning = !font.manualKerning; + /* + https://developer.mozilla.org/en/docs/Web/CSS/vertical-align + https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html + https://www.microsoft.com/typography/otspec/base.htm + http://apike.ca/prog_svg_text_style.html + https://www.w3schools.com/tags/canvas_textbaseline.asp + http://vanseodesign.com/web-design/svg-text-baseline-alignment/ + https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align + https://tympanus.net/codrops/css_reference/vertical-align/ + + https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty + 11.10.2.6. The ‘alignment-baseline’ property + + This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3] + https://drafts.csswg.org/css-inline/#propdef-alignment-baseline + + The vertical-align property shorthand should be preferred in new content. + + SVG 2 introduces some changes to the definition of this property. + In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed. + For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and + 'text-after-edge' should be mapped to 'text-bottom'. + + Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. + */ Paint.FontMetrics fm = paint.getFontMetrics(); - double top = fm.top; + double top = -fm.top; double bottom = fm.bottom; - double ascent = fm.ascent; - double descent = fm.descent; - double totalHeight = top - bottom; + double ascenderHeight = -fm.ascent; + double descenderDepth = fm.descent; + double totalHeight = top + bottom; + double baselineShift = 0; + if (mAlignmentBaseline != null) { + // TODO alignment-baseline + switch (mAlignmentBaseline) { + // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + default: + case baseline: + // Use the dominant baseline choice of the parent. + // Match the box’s corresponding baseline to that of its parent. + baselineShift = 0; + break; + + case textBottom: + case afterEdge: + case textAfterEdge: + // Match the bottom of the box to the bottom of the parent’s content area. + // text-after-edge = text-bottom + // text-after-edge = descender depth + baselineShift = descenderDepth; + break; + + case alphabetic: + // Match the box’s alphabetic baseline to that of its parent. + // alphabetic = 0 + baselineShift = 0; + break; + + case ideographic: + // Match the box’s ideographic character face under-side baseline to that of its parent. + // ideographic = descender depth + baselineShift = descenderDepth; + break; + + case middle: + // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. + // middle = x height / 2 + Rect bounds = new Rect(); + // this will just retrieve the bounding rect for 'x' + paint.getTextBounds("x", 0, 1, bounds); + int xHeight = bounds.height(); + baselineShift = xHeight / 2; + break; + + case central: + // Match the box’s central baseline to the central baseline of its parent. + // central = (ascender height - descender depth) / 2 + baselineShift = (ascenderHeight - descenderDepth) / 2; + break; + + case mathematical: + // Match the box’s mathematical baseline to that of its parent. + // Hanging and mathematical baselines + // There are no obvious formulas to calculate the position of these baselines. + // At the time of writing FOP puts the hanging baseline at 80% of the ascender + // height and the mathematical baseline at 50%. + baselineShift = ascenderHeight / 2; + break; + + case hanging: + baselineShift = 0.8 * ascenderHeight; + break; + + case textTop: + case beforeEdge: + case textBeforeEdge: + // Match the top of the box to the top of the parent’s content area. + // text-before-edge = text-top + // text-before-edge = ascender height + baselineShift = ascenderHeight; + break; + + case bottom: + // Align the top of the aligned subtree with the top of the line box. + baselineShift = bottom; + break; + + case center: + // Align the center of the aligned subtree with the center of the line box. + baselineShift = totalHeight / 2; + break; + + case top: + // Align the bottom of the aligned subtree with the bottom of the line box. + baselineShift = top; + break; + } + } final char[] chars = line.toCharArray(); for (int index = 0; index < length; index++) { @@ -211,7 +325,6 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci adjustments are calculated as distance adjustments along the path, calculated using the user agent's distance along the path algorithm. */ - if (autoKerning) { double bothCharsWidth = paint.measureText(previous + current) * renderMethodScaling; kerning = bothCharsWidth - previousCharWidth - charWidth; @@ -272,18 +385,21 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci */ assert pm != null; if (startpoint < 0 || endpoint > distance) { - /* - In the calculation above, if either the startpoint-on-the-path - or the endpoint-on-the-path is off the end of the path, - TODO then extend the path beyond its end points with a straight line - that is parallel to the tangent at the path at its end point - so that the midpoint-on-the-path can still be calculated. - - TODO suggest change in wording of svg spec: - so that the midpoint-on-the-path can still be calculated - to - so that the angle of the glyph-midline to the x-axis can still be calculated - */ + /* + In the calculation above, if either the startpoint-on-the-path + or the endpoint-on-the-path is off the end of the path, + TODO then extend the path beyond its end points with a straight line + that is parallel to the tangent at the path at its end point + so that the midpoint-on-the-path can still be calculated. + + TODO suggest change in wording of svg spec: + so that the midpoint-on-the-path can still be calculated. + to + so that the angle of the glyph-midline to the x-axis can still be calculated. + or + so that the line through the startpoint-on-the-path and the + endpoint-on-the-path can still be calculated. + */ final int flags = POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG; pm.getMatrix((float) midpoint, mid, flags); } else { @@ -294,127 +410,26 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci start.getValues(startPointMatrixData); end.getValues(endPointMatrixData); - double startX = startPointMatrixData[2]; - double startY = startPointMatrixData[5]; - double endX = endPointMatrixData[2]; - double endY = endPointMatrixData[5]; + double startX = startPointMatrixData[MTRANS_X]; + double startY = startPointMatrixData[MTRANS_Y]; + double endX = endPointMatrixData[MTRANS_X]; + double endY = endPointMatrixData[MTRANS_Y]; + + /* + line through the startpoint-on-the-path and the endpoint-on-the-path + */ + double lineX = endX - startX; + double lineY = endY - startY; - double glyphMidlineAngle = Math.atan2(endY - startY, endX - startX) * radToDeg; + double glyphMidlineAngle = Math.atan2(lineY, lineX); - mid.preRotate((float) glyphMidlineAngle); + mid.preRotate((float) (glyphMidlineAngle * radToDeg)); } /* - TODO alignment-baseline Align the glyph vertically relative to the midpoint-on-the-path based on property alignment-baseline and any specified values for attribute ‘dy’ on a ‘tspan’ element. - - https://developer.mozilla.org/en/docs/Web/CSS/vertical-align - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html - https://www.microsoft.com/typography/otspec/base.htm - http://apike.ca/prog_svg_text_style.html - https://www.w3schools.com/tags/canvas_textbaseline.asp - http://vanseodesign.com/web-design/svg-text-baseline-alignment/ - https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align - https://tympanus.net/codrops/css_reference/vertical-align/ - - 11.10.2.6. The ‘alignment-baseline’ property - - This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3] - - The vertical-align property shorthand should be preferred in new content. - - SVG 2 introduces some changes to the definition of this property. - In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed. - For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and - 'text-after-edge' should be mapped to 'text-bottom'. - - Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. - - SVG implementations may support the following aliases in order to support legacy content: - TODO? - text-before-edge = text-top - text-after-edge = text-bottom */ - double baselineShift = 0; - if (mAlignmentBaseline != null) { - switch (mAlignmentBaseline) { - // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling - default: - case baseline: - // Use the dominant baseline choice of the parent. - // Match the box’s corresponding baseline to that of its parent. - baselineShift = 0; - break; - - case textBottom: - // Match the bottom of the box to the bottom of the parent’s content area. - // text-after-edge = text-bottom - // text-after-edge = descender depth - baselineShift = descent; - break; - - case alphabetic: - // Match the box’s alphabetic baseline to that of its parent. - // alphabetic = 0 - baselineShift = 0; - break; - - case ideographic: - // Match the box’s ideographic character face under-side baseline to that of its parent. - // ideographic = descender depth - baselineShift = descent; - break; - - case middle: - // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. - // middle = x height / 2 - Rect bounds = new Rect(); - // this will just retrieve the bounding rect for 'x' - paint.getTextBounds("x", 0, 1, bounds); - int xHeight = bounds.height(); - baselineShift = xHeight / 2; - break; - - case central: - // Match the box’s central baseline to the central baseline of its parent. - // central = (ascender height - descender depth) / 2 - baselineShift = (ascent - descent) / 2; - break; - - case mathematical: - // Match the box’s mathematical baseline to that of its parent. - // Hanging and mathematical baselines - // There are no obvious formulas to calculate the position of these baselines. - // At the time of writing FOP puts the hanging baseline at 80% of the ascender - // height and the mathematical baseline at 50%. - baselineShift = ascent / 2; - break; - - case textTop: - // Match the top of the box to the top of the parent’s content area. - // text-before-edge = text-top - // text-before-edge = ascender height - baselineShift = ascent; - break; - - case bottom: - // Align the top of the aligned subtree with the top of the line box. - baselineShift = bottom; - break; - - case center: - // Align the center of the aligned subtree with the center of the line box. - baselineShift = totalHeight / 2; - break; - - case top: - // Align the bottom of the aligned subtree with the bottom of the line box. - baselineShift = top; - break; - } - } - mid.preTranslate((float) -halfway, (float) (dy - baselineShift)); mid.preScale((float) renderMethodScaling, (float) renderMethodScaling); mid.postTranslate(0, (float) y); @@ -483,6 +498,8 @@ private void applyTextPropertiesToPaint(Paint paint, FontData font) { paint.setTypeface(typeface); paint.setTextSize((float) fontSize); paint.setTextAlign(Paint.Align.LEFT); + + // Do these have any effect for anyone? Not for me (@msand) at least. paint.setUnderlineText(underlineText); paint.setStrikeThruText(strikeThruText); } diff --git a/lib/props.js b/lib/props.js index 585910417..0c14884fb 100644 --- a/lib/props.js +++ b/lib/props.js @@ -175,7 +175,9 @@ const textLength = PropTypes.string; /* Name: alignment-baseline - Value: baseline | text-bottom | alphabetic | ideographic | middle | central | mathematical | text-top | bottom | center | top + + 1.1 Value: auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | inherit + 2.0 Value: baseline | text-bottom | alphabetic | ideographic | middle | central | mathematical | text-top | bottom | center | top Initial: baseline Applies to: inline-level boxes, flex items, grid items, table cells Inherited: no @@ -185,10 +187,11 @@ const textLength = PropTypes.string; Canonical order: per grammar Animation type: discrete https://drafts.csswg.org/css-inline/#propdef-alignment-baseline + https://www.w3.org/TR/SVG11/text.html#AlignmentBaselineProperty https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline */ -const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabetic', 'ideographic', 'middle', 'central', 'mathematical', 'text-top', 'bottom', 'center', 'top']); +const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabetic', 'ideographic', 'middle', 'central', 'mathematical', 'text-top', 'bottom', 'center', 'top', 'text-before-edge', 'text-after-edge', 'before-edge', 'after-edge', 'hanging']); const textSpecificProps = { ...pathProps, From 51f28ce525fd24c10e63aab06c002881643e342b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 10:16:58 +0300 Subject: [PATCH 136/198] Add missing enum file --- android/src/main/java/com/horcrux/svg/TextPathSide.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 android/src/main/java/com/horcrux/svg/TextPathSide.java diff --git a/android/src/main/java/com/horcrux/svg/TextPathSide.java b/android/src/main/java/com/horcrux/svg/TextPathSide.java new file mode 100644 index 000000000..41348dcfc --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextPathSide.java @@ -0,0 +1,6 @@ +package com.horcrux.svg; + +enum TextPathSide { + left, + right +} From d2eb3a6e160fae0be1a275113a4899fa725043b5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 10:19:11 +0300 Subject: [PATCH 137/198] Fix todo comment --- android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 858211038..8c4895659 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -219,7 +219,7 @@ private Path getLinePath(String line, Paint paint) { double totalHeight = top + bottom; double baselineShift = 0; if (mAlignmentBaseline != null) { - // TODO alignment-baseline + // TODO alignment-baseline, test / verify behavior switch (mAlignmentBaseline) { // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling default: From 9e0de800bd20a2225de01c99f0e5bab7d888df7b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 11:06:01 +0300 Subject: [PATCH 138/198] Refactor, optimize. --- .../main/java/com/horcrux/svg/FontData.java | 1 - .../java/com/horcrux/svg/TSpanShadowNode.java | 48 +++++++++++-------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index c8e643034..7c10bb117 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -19,7 +19,6 @@ class FontData { private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; private static final String TEXT_DECORATION = "textDecoration"; - private static final String ALIGNMENT_BASELINE = "alignmentBaseline"; final double fontSize; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 8c4895659..b50435edd 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -122,18 +122,8 @@ private Path getLinePath(String line, Paint paint) { is adjusted to take into account various horizontal alignment text properties and attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. */ - double offset; - final TextAnchor textAnchor = font.textAnchor; final double textMeasure = paint.measureText(line); - if (textAnchor == TextAnchor.start) { - offset = 0; - } else { - if (textAnchor == TextAnchor.middle) { - offset = -textMeasure / 2; - } else { - offset = -textMeasure; - } - } + double offset = getTextAnchorOffset(font.textAnchor, textMeasure); double distance = 0; PathMeasure pm = null; @@ -144,9 +134,7 @@ private Path getLinePath(String line, Paint paint) { if (distance == 0) { return path; } - final double size = gc.getFontSize(); - final String startOffset = textPath.getStartOffset(); - offset += PropHelper.fromRelative(startOffset, distance, 0, mScale, size); + offset += getAbsoluteStartOffset(distance, gc.getFontSize(), textPath.getStartOffset()); /* TextPathSpacing spacing = textPath.getSpacing(); if (spacing == TextPathSpacing.auto) { @@ -305,6 +293,13 @@ private Path getLinePath(String line, Paint paint) { } } + Matrix start = new Matrix(); + Matrix mid = new Matrix(); + Matrix end = new Matrix(); + + float[] startPointMatrixData = new float[9]; + float[] endPointMatrixData = new float[9]; + final char[] chars = line.toCharArray(); for (int index = 0; index < length; index++) { char currentChar = chars[index]; @@ -342,13 +337,6 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci double dy = gc.nextDeltaY(); double r = gc.nextRotation(); - Matrix start = new Matrix(); - Matrix mid = new Matrix(); - Matrix end = new Matrix(); - - float[] startPointMatrixData = new float[9]; - float[] endPointMatrixData = new float[9]; - double startpoint = offset + x + dx - charWidth; if (textPath != null) { @@ -448,6 +436,24 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci return path; } + private double getAbsoluteStartOffset(double distance, double size, String startOffset) { + return PropHelper.fromRelative(startOffset, distance, 0, mScale, size); + } + + private double getTextAnchorOffset(TextAnchor textAnchor, double textMeasure) { + switch (textAnchor) { + default: + case start: + return 0; + + case middle: + return -textMeasure / 2; + + case end: + return -textMeasure; + } + } + private void applyTextPropertiesToPaint(Paint paint, FontData font) { AssetManager assetManager = getThemedContext().getResources().getAssets(); From 48cfe71acaad96e484ce46065c3fab497f37a168 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 19:46:02 +0300 Subject: [PATCH 139/198] Implement GlyphPathBag for caching Paths per char, after a applyTextPropertiesToPaint(paint, font) Path glyph = bag.getOrCreateAndCache(currentChar, current); --- .../java/com/horcrux/svg/GlyphPathBag.java | 48 +++++++++++++++++++ .../java/com/horcrux/svg/TSpanShadowNode.java | 18 ++++--- 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/GlyphPathBag.java diff --git a/android/src/main/java/com/horcrux/svg/GlyphPathBag.java b/android/src/main/java/com/horcrux/svg/GlyphPathBag.java new file mode 100644 index 000000000..0b411dd30 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/GlyphPathBag.java @@ -0,0 +1,48 @@ +package com.horcrux.svg; + +import android.graphics.Paint; +import android.graphics.Path; + +import java.util.ArrayList; + +class GlyphPathBag { + private ArrayList paths = new ArrayList<>(); + private int[][] data = new int[255][]; + 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[255]; + } + 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]; + } +} diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index b50435edd..f9467afa8 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -98,6 +98,7 @@ private Path getLinePath(String line, Paint paint) { GlyphContext gc = getTextRootGlyphContext(); FontData font = gc.getFont(); applyTextPropertiesToPaint(paint, font); + GlyphPathBag bag = new GlyphPathBag(paint); /* Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ @@ -127,7 +128,6 @@ private Path getLinePath(String line, Paint paint) { double distance = 0; PathMeasure pm = null; - double renderMethodScaling = 1; if (textPath != null) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); @@ -142,12 +142,10 @@ private Path getLinePath(String line, Paint paint) { // https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute } */ - final TextPathMethod method = textPath.getMethod(); - if (method == TextPathMethod.stretch) { - renderMethodScaling = distance / textMeasure; - } } + double renderMethodScaling = getRenderMethodScaling(textMeasure, distance); + /* * * Three properties affect the space between characters and words: @@ -427,8 +425,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci mid.preRotate((float) r); - Path glyph = new Path(); - paint.getTextPath(current, 0, 1, 0, 0, glyph); + Path glyph = bag.getOrCreateAndCache(currentChar, current); glyph.transform(mid); path.addPath(glyph); } @@ -436,6 +433,13 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci return path; } + private double getRenderMethodScaling(double textMeasure, double distance) { + if (textPath != null && textPath.getMethod() == TextPathMethod.stretch) { + return distance / textMeasure; + } + return 1; + } + private double getAbsoluteStartOffset(double distance, double size, String startOffset) { return PropHelper.fromRelative(startOffset, distance, 0, mScale, size); } From 54efe3ad1e957a094f22d903c359f5e75d63e681 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 21:58:07 +0300 Subject: [PATCH 140/198] Fix strokeDasharray handling of odd number of length values and the 'none' value. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 6 +++--- lib/extract/extractStroke.js | 15 ++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index f9467afa8..77ef4cbab 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -134,7 +134,7 @@ private Path getLinePath(String line, Paint paint) { if (distance == 0) { return path; } - offset += getAbsoluteStartOffset(distance, gc.getFontSize(), textPath.getStartOffset()); + offset += getAbsoluteStartOffset(textPath.getStartOffset(), distance, gc.getFontSize()); /* TextPathSpacing spacing = textPath.getSpacing(); if (spacing == TextPathSpacing.auto) { @@ -440,8 +440,8 @@ private double getRenderMethodScaling(double textMeasure, double distance) { return 1; } - private double getAbsoluteStartOffset(double distance, double size, String startOffset) { - return PropHelper.fromRelative(startOffset, distance, 0, mScale, size); + private double getAbsoluteStartOffset(String startOffset, double distance, double fontSize) { + return PropHelper.fromRelative(startOffset, distance, 0, mScale, fontSize); } private double getTextAnchorOffset(TextAnchor textAnchor, double textMeasure) { diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index ccc93b71f..05da84fe1 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -29,13 +29,18 @@ export default function(props, styleProperties) { const strokeWidth = props.strokeWidth; let strokeDasharray = props.strokeDasharray; - if (typeof strokeDasharray === 'string') { + if (!strokeDasharray || strokeDasharray === 'none') { + strokeDasharray = null; + } else if (typeof strokeDasharray === 'string') { strokeDasharray = strokeDasharray.split(separator).map(dash => +dash); } - // strokeDasharray length must be more than 1. - if (strokeDasharray && strokeDasharray.length === 1) { - strokeDasharray.push(strokeDasharray[0]); + // 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); } return { @@ -43,7 +48,7 @@ export default function(props, styleProperties) { strokeOpacity: extractOpacity(props.strokeOpacity), strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, - strokeDasharray: strokeDasharray || null, + strokeDasharray: strokeDasharray, strokeWidth: strokeWidth || '1', strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, strokeMiterlimit: props.strokeMiterlimit || 4 From a8fdf6482873e0cd2c9f259cdd53929af01b8a0f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 25 Jul 2017 22:37:13 +0300 Subject: [PATCH 141/198] Ensure strokeWidth is string --- lib/extract/extractStroke.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 05da84fe1..5c774e15c 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -49,7 +49,7 @@ export default function(props, styleProperties) { strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeDasharray: strokeDasharray, - strokeWidth: strokeWidth || '1', + strokeWidth: `${strokeWidth || 1}`, strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, strokeMiterlimit: props.strokeMiterlimit || 4 }; From d08103168a4ee78c163f2614e5f3e8e7cede6b9d Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 26 Jul 2017 03:51:57 +0300 Subject: [PATCH 142/198] Suggest adding a compatibility mid-line rendering attribute to textPath. --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 10 +++++++++- .../main/java/com/horcrux/svg/TextPathMidLine.java | 12 ++++++++++++ .../java/com/horcrux/svg/TextPathShadowNode.java | 11 +++++++++++ lib/attributes.js | 1 + lib/props.js | 13 +++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/com/horcrux/svg/TextPathMidLine.java diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 77ef4cbab..01e67ff80 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -29,6 +29,7 @@ import static android.graphics.Matrix.MTRANS_Y; import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG; import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG; +import static com.horcrux.svg.TextPathMidLine.sharp; /** * Shadow node for virtual TSpan view @@ -128,7 +129,9 @@ private Path getLinePath(String line, Paint paint) { double distance = 0; PathMeasure pm = null; + boolean sharpMidline = false; if (textPath != null) { + sharpMidline = textPath.getMidLine() == sharp; pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); if (distance == 0) { @@ -368,9 +371,14 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci Position the glyph such that the glyph-midline passes through the midpoint-on-the-path and is perpendicular to the line through the startpoint-on-the-path and the endpoint-on-the-path. + + TODO suggest adding a compatibility mid-line rendering attribute to textPath, + for a chrome/firefox/opera/safari compatible sharp text path rendering, + which doesn't bend text smoothly along a right angle curve, (like Edge does) + but keeps the mid-line orthogonal to the mid-point tangent at all times instead. */ assert pm != null; - if (startpoint < 0 || endpoint > distance) { + if (startpoint < 0 || endpoint > distance || sharpMidline) { /* In the calculation above, if either the startpoint-on-the-path or the endpoint-on-the-path is off the end of the path, diff --git a/android/src/main/java/com/horcrux/svg/TextPathMidLine.java b/android/src/main/java/com/horcrux/svg/TextPathMidLine.java new file mode 100644 index 000000000..764a64782 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextPathMidLine.java @@ -0,0 +1,12 @@ +package com.horcrux.svg; + +/* + TODO suggest adding a compatibility mid-line rendering attribute to textPath, + for a chrome/firefox/opera/safari compatible sharp text path rendering, + which doesn't bend text smoothly along a right angle curve, (like Edge does) + but keeps the mid-line orthogonal to the mid-point tangent at all times instead. +*/ +enum TextPathMidLine { + sharp, + smooth +} diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index 6223e1f77..6c976b7a4 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -23,6 +23,7 @@ class TextPathShadowNode extends TextShadowNode { private String mHref; + private TextPathMidLine mMidLine; private @Nullable String mStartOffset; private TextPathMethod mMethod = TextPathMethod.align; private TextPathSpacing mSpacing = TextPathSpacing.exact; @@ -58,6 +59,12 @@ public void setSide(@Nullable String side) { markUpdated(); } + @ReactProp(name = "midLine") + public void setSharp(@Nullable String midLine) { + mMidLine = TextPathMidLine.valueOf(midLine); + markUpdated(); + } + TextPathMethod getMethod() { return mMethod; } @@ -70,6 +77,10 @@ public TextPathSide getSide() { return mSide; } + public TextPathMidLine getMidLine() { + return mMidLine; + } + String getStartOffset() { return mStartOffset; } diff --git a/lib/attributes.js b/lib/attributes.js index 8854e66b9..9865a0691 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -134,6 +134,7 @@ const TextPathAttributes = { method: true, spacing: true, side: true, + midLine: true, }; const TSpanAttibutes = { diff --git a/lib/props.js b/lib/props.js index 0c14884fb..51fda0034 100644 --- a/lib/props.js +++ b/lib/props.js @@ -263,6 +263,18 @@ const method = PropTypes.oneOf(['align', 'stretch']); */ const spacing = PropTypes.oneOf(['auto', 'exact']); +/* + Name + mid-line + Value + sharp | smooth + initial value + smooth + Animatable + yes + */ +const midLine = PropTypes.oneOf(['sharp', 'smooth']); + // https://svgwg.org/svg2-draft/text.html#TextPathAttributes // https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath const textPathProps = { @@ -272,6 +284,7 @@ const textPathProps = { method, spacing, side, + midLine, }; export { From fab3afb03a913c616e10dbef0fc2fc0080fbfb10 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 26 Jul 2017 21:05:34 +0300 Subject: [PATCH 143/198] Implement textPath side attribute. Refactor extractStroke. --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 12 ++++++++---- .../java/com/horcrux/svg/TextPathShadowNode.java | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 01e67ff80..1b6582f19 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -127,6 +127,7 @@ private Path getLinePath(String line, Paint paint) { final double textMeasure = paint.measureText(line); double offset = getTextAnchorOffset(font.textAnchor, textMeasure); + final int side = textPath.getSide() == TextPathSide.right ? -1 : 1; double distance = 0; PathMeasure pm = null; boolean sharpMidline = false; @@ -148,6 +149,7 @@ private Path getLinePath(String line, Paint paint) { } double renderMethodScaling = getRenderMethodScaling(textMeasure, distance); + double scaledDirection = renderMethodScaling * side; /* * @@ -338,7 +340,9 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci double dy = gc.nextDeltaY(); double r = gc.nextRotation(); - double startpoint = offset + x + dx - charWidth; + charWidth = charWidth * side; + double cursor = offset + (x + dx) * side; + double startpoint = cursor - charWidth; if (textPath != null) { /* @@ -347,7 +351,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci distance along the path algorithm. This point is the endpoint-on-the-path for the glyph. */ - double endpoint = startpoint + charWidth; + double endpoint = cursor; /* Determine the midpoint-on-the-path, which is the point on the path which is @@ -417,7 +421,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci double glyphMidlineAngle = Math.atan2(lineY, lineX); - mid.preRotate((float) (glyphMidlineAngle * radToDeg)); + mid.preRotate((float) (glyphMidlineAngle * radToDeg * side)); } /* @@ -425,7 +429,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci alignment-baseline and any specified values for attribute ‘dy’ on a ‘tspan’ element. */ mid.preTranslate((float) -halfway, (float) (dy - baselineShift)); - mid.preScale((float) renderMethodScaling, (float) renderMethodScaling); + mid.preScale((float) scaledDirection, (float) scaledDirection); mid.postTranslate(0, (float) y); } else { mid.setTranslate((float) startpoint, (float) (y + dy)); diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index 6c976b7a4..c43e67ec4 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -23,11 +23,11 @@ class TextPathShadowNode extends TextShadowNode { private String mHref; + private TextPathSide mSide; private TextPathMidLine mMidLine; private @Nullable String mStartOffset; private TextPathMethod mMethod = TextPathMethod.align; private TextPathSpacing mSpacing = TextPathSpacing.exact; - private TextPathSide mSide; @ReactProp(name = "href") public void setHref(String href) { From 36595c91e2ca29b486664bc1b06d170d70faa7af Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 27 Jul 2017 00:06:46 +0300 Subject: [PATCH 144/198] Correct start- and end-point calculation. Implement lengthAdjust and textLength attribute. Fix start and end rendering cutoff for text-anchor on a path. Refactor extractStroke. Improve docs. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 275 ++++++++++++++---- .../com/horcrux/svg/TextLengthAdjust.java | 7 + .../java/com/horcrux/svg/TextShadowNode.java | 14 + 3 files changed, 241 insertions(+), 55 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/TextLengthAdjust.java diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 1b6582f19..55412697e 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -80,7 +80,7 @@ protected Path getPath(Canvas canvas, Paint paint) { setupTextPath(); pushGlyphContext(); - mCache = getLinePath(mContent, paint); + mCache = getLinePath(mContent, paint, canvas); popGlyphContext(); mCache.computeBounds(new RectF(), true); @@ -88,7 +88,7 @@ protected Path getPath(Canvas canvas, Paint paint) { return mCache; } - private Path getLinePath(String line, Paint paint) { + private Path getLinePath(String line, Paint paint, Canvas canvas) { final int length = line.length(); final Path path = new Path(); @@ -96,10 +96,22 @@ private Path getLinePath(String line, Paint paint) { return path; } + double distance = 0; + PathMeasure pm = null; + final boolean hasTextPath = textPath != null; + if (hasTextPath) { + pm = new PathMeasure(textPath.getPath(), false); + distance = pm.getLength(); + if (distance == 0) { + return path; + } + } + GlyphContext gc = getTextRootGlyphContext(); FontData font = gc.getFont(); applyTextPropertiesToPaint(paint, font); GlyphPathBag bag = new GlyphPathBag(paint); + final char[] chars = line.toCharArray(); /* Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ @@ -124,21 +136,80 @@ private Path getLinePath(String line, Paint paint) { is adjusted to take into account various horizontal alignment text properties and attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. */ + final TextAnchor textAnchor = font.textAnchor; final double textMeasure = paint.measureText(line); - double offset = getTextAnchorOffset(font.textAnchor, textMeasure); + double offset = getTextAnchorOffset(textAnchor, textMeasure); + + int side = 1; + double endOfRendering = 0; + double startOfRendering = 0; + boolean sharpMidLine = false; + final double fontSize = gc.getFontSize(); + if (hasTextPath) { + sharpMidLine = textPath.getMidLine() == sharp; + /* + Name + side + Value + left | right + initial value + left + Animatable + yes - final int side = textPath.getSide() == TextPathSide.right ? -1 : 1; - double distance = 0; - PathMeasure pm = null; - boolean sharpMidline = false; - if (textPath != null) { - sharpMidline = textPath.getMidLine() == sharp; - pm = new PathMeasure(textPath.getPath(), false); - distance = pm.getLength(); - if (distance == 0) { - return path; - } - offset += getAbsoluteStartOffset(textPath.getStartOffset(), distance, gc.getFontSize()); + Determines the side of the path the text is placed on + (relative to the path direction). + + Specifying a value of right effectively reverses the path. + + Added in SVG 2 to allow text either inside or outside closed subpaths + and basic shapes (e.g. rectangles, circles, and ellipses). + + Adding 'side' was resolved at the Sydney (2015) meeting. + */ + side = textPath.getSide() == TextPathSide.right ? -1 : 1; + /* + Name + startOffset + Value + | | + initial value + 0 + Animatable + yes + + An offset from the start of the path for the initial current text position, + calculated using the user agent's distance along the path algorithm, + after converting the path to the ‘textPath’ element's coordinate system. + + If a other than a percentage is given, then the ‘startOffset’ + represents a distance along the path measured in the current user coordinate + system for the ‘textPath’ element. + + If a percentage is given, then the ‘startOffset’ represents a percentage + distance along the entire path. Thus, startOffset="0%" indicates the start + point of the path and startOffset="100%" indicates the end point of the path. + + Negative values and values larger than the path length (e.g. 150%) are allowed. + + Any typographic characters with mid-points that are not on the path are not rendered + + For paths consisting of a single closed subpath (including an equivalent path for a + basic shape), typographic characters are rendered along one complete circuit of the + path. The text is aligned as determined by the text-anchor property to a position + along the path set by the ‘startOffset’ attribute. + + For the start (end) value, the text is rendered from the start (end) of the line + until the initial position along the path is reached again. + + For the middle, the text is rendered from the middle point in both directions until + a point on the path equal distance in both directions from the initial position on + the path is reached. + */ + final double halfPathDistance = distance / 2; + offset += getAbsoluteStartOffset(textPath.getStartOffset(), distance, fontSize); + startOfRendering = -offset + (textAnchor == TextAnchor.middle ? -halfPathDistance : 0); + endOfRendering = startOfRendering + distance; /* TextPathSpacing spacing = textPath.getSpacing(); if (spacing == TextPathSpacing.auto) { @@ -148,8 +219,38 @@ private Path getLinePath(String line, Paint paint) { */ } - double renderMethodScaling = getRenderMethodScaling(textMeasure, distance); - double scaledDirection = renderMethodScaling * side; + /* + Name + method + Value + align | stretch + initial value + align + Animatable + yes + Indicates the method by which text should be rendered along the path. + + A value of align indicates that the typographic character should be rendered using + simple 2×3 matrix transformations such that there is no stretching/warping of the + typographic characters. Typically, supplemental rotation, scaling and translation + transformations are done for each typographic characters to be rendered. + + As a result, with align, in fonts where the typographic characters are designed to be + connected (e.g., cursive fonts), the connections may not align properly when text is + rendered along a path. + + A value of stretch indicates that the typographic character outlines will be converted + into paths, and then all end points and control points will be adjusted to be along the + perpendicular vectors from the path, thereby stretching and possibly warping the glyphs. + + With this approach, connected typographic characters, such as in cursive scripts, + will maintain their connections. (Non-vertical straight path segments should be + converted to Bézier curves in such a way that horizontal straight paths have an + (approximately) constant offset from the path along which the typographic characters + are rendered.) + + TODO implement stretch + */ /* * @@ -170,13 +271,72 @@ private Path getLinePath(String line, Paint paint) { * or decrease the space between typographic character units in order to justify text. * * */ - String previous = ""; - double previousCharWidth = 0; double kerning = font.kerning; - final double wordSpacing = font.wordSpacing; + double wordSpacing = font.wordSpacing; final double letterSpacing = font.letterSpacing; final boolean autoKerning = !font.manualKerning; + /* + Name Value Initial value Animatable + textLength | | See below yes + + The author's computation of the total sum of all of the advance values that correspond + to character data within this element, including the advance value on the glyph + (horizontal or vertical), the effect of properties letter-spacing and word-spacing and + adjustments due to attributes ‘dx’ and ‘dy’ on this ‘text’ or ‘tspan’ element or any + descendants. This value is used to calibrate the user agent's own calculations with + that of the author. + + The purpose of this attribute is to allow the author to achieve exact alignment, + in visual rendering order after any bidirectional reordering, for the first and + last rendered glyphs that correspond to this element; thus, for the last rendered + character (in visual rendering order after any bidirectional reordering), + any supplemental inter-character spacing beyond normal glyph advances are ignored + (in most cases) when the user agent determines the appropriate amount to expand/compress + the text string to fit within a length of ‘textLength’. + + If attribute ‘textLength’ is specified on a given element and also specified on an + ancestor, the adjustments on all character data within this element are controlled by + the value of ‘textLength’ on this element exclusively, with the possible side-effect + that the adjustment ratio for the contents of this element might be different than the + adjustment ratio used for other content that shares the same ancestor. The user agent + must assume that the total advance values for the other content within that ancestor is + the difference between the advance value on that ancestor and the advance value for + this element. + + This attribute is not intended for use to obtain effects such as shrinking or + expanding text. + + A negative value is an error (see Error processing). + + The ‘textLength’ attribute is only applied when the wrapping area is not defined by the + shape-inside or the inline-size properties. It is also not applied for any ‘text’ or + ‘tspan’ element that has forced line breaks (due to a white-space value of pre or + pre-line). + + If the attribute is not specified anywhere within a ‘text’ element, the effect is as if + the author's computation exactly matched the value calculated by the user agent; + thus, no advance adjustments are made. + */ + double scaleSpacingAndGlyphs = 1; + if (mTextLength != null) { + final double author = PropHelper.fromRelative(mTextLength, canvas.getWidth(), 0, mScale, fontSize); + if (author < 0) { + throw new IllegalArgumentException("Negative textLength value"); + } + switch (mLengthAdjust) { + default: + case spacing: + int numSpaces = getNumSpaces(length, chars); + wordSpacing += (author - textMeasure) / numSpaces; + break; + case spacingAndGlyphs: + scaleSpacingAndGlyphs = author / textMeasure; + break; + } + } + final double scaledDirection = scaleSpacingAndGlyphs * side; + /* https://developer.mozilla.org/en/docs/Web/CSS/vertical-align https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html @@ -202,15 +362,16 @@ private Path getLinePath(String line, Paint paint) { Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. */ - Paint.FontMetrics fm = paint.getFontMetrics(); - double top = -fm.top; - double bottom = fm.bottom; - double ascenderHeight = -fm.ascent; - double descenderDepth = fm.descent; - double totalHeight = top + bottom; + final Paint.FontMetrics fm = paint.getFontMetrics(); + final double top = -fm.top; + final double bottom = fm.bottom; + final double ascenderHeight = -fm.ascent; + final double descenderDepth = fm.descent; + final double totalHeight = top + bottom; double baselineShift = 0; if (mAlignmentBaseline != null) { // TODO alignment-baseline, test / verify behavior + // TODO get per glyph baselines from font baseline table, for high-precision alignment switch (mAlignmentBaseline) { // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling default: @@ -296,14 +457,15 @@ private Path getLinePath(String line, Paint paint) { } } - Matrix start = new Matrix(); - Matrix mid = new Matrix(); - Matrix end = new Matrix(); + final Matrix start = new Matrix(); + final Matrix mid = new Matrix(); + final Matrix end = new Matrix(); - float[] startPointMatrixData = new float[9]; - float[] endPointMatrixData = new float[9]; + final float[] startPointMatrixData = new float[9]; + final float[] endPointMatrixData = new float[9]; - final char[] chars = line.toCharArray(); + String previous = ""; + double previousCharWidth = 0; for (int index = 0; index < length; index++) { char currentChar = chars[index]; String current = String.valueOf(currentChar); @@ -312,7 +474,7 @@ private Path getLinePath(String line, Paint paint) { Determine the glyph's charwidth (i.e., the amount which the current text position advances horizontally when the glyph is drawn using horizontal text layout). */ - double charWidth = paint.measureText(current) * renderMethodScaling; + double charWidth = paint.measureText(current) * scaleSpacingAndGlyphs; /* For each subsequent glyph, set a new startpoint-on-the-path as the previous @@ -324,7 +486,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci using the user agent's distance along the path algorithm. */ if (autoKerning) { - double bothCharsWidth = paint.measureText(previous + current) * renderMethodScaling; + double bothCharsWidth = paint.measureText(previous + current) * scaleSpacingAndGlyphs; kerning = bothCharsWidth - previousCharWidth - charWidth; previousCharWidth = charWidth; previous = current; @@ -340,31 +502,32 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci double dy = gc.nextDeltaY(); double r = gc.nextRotation(); + advance = advance * side; charWidth = charWidth * side; double cursor = offset + (x + dx) * side; - double startpoint = cursor - charWidth; + double startPoint = cursor - advance; - if (textPath != null) { + if (hasTextPath) { /* Determine the point on the curve which is charwidth distance along the path from the startpoint-on-the-path for this glyph, calculated using the user agent's distance along the path algorithm. This point is the endpoint-on-the-path for the glyph. */ - double endpoint = cursor; + double endPoint = startPoint + charWidth; /* Determine the midpoint-on-the-path, which is the point on the path which is "halfway" (user agents can choose either a distance calculation or a parametric calculation) between the startpoint-on-the-path and the endpoint-on-the-path. */ - double halfway = charWidth / 2; - double midpoint = startpoint + halfway; + double halfWay = charWidth / 2; + double midPoint = startPoint + halfWay; // Glyphs whose midpoint-on-the-path are off the path are not rendered. - if (midpoint > distance) { - break; - } else if (midpoint < 0) { + if (midPoint > endOfRendering) { + continue; + } else if (midPoint < startOfRendering) { continue; } @@ -381,8 +544,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci which doesn't bend text smoothly along a right angle curve, (like Edge does) but keeps the mid-line orthogonal to the mid-point tangent at all times instead. */ - assert pm != null; - if (startpoint < 0 || endpoint > distance || sharpMidline) { + if (startPoint < 0 || endPoint > distance || sharpMidLine) { /* In the calculation above, if either the startpoint-on-the-path or the endpoint-on-the-path is off the end of the path, @@ -399,11 +561,11 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci endpoint-on-the-path can still be calculated. */ final int flags = POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG; - pm.getMatrix((float) midpoint, mid, flags); + pm.getMatrix((float) midPoint, mid, flags); } else { - pm.getMatrix((float) startpoint, start, POSITION_MATRIX_FLAG); - pm.getMatrix((float) midpoint, mid, POSITION_MATRIX_FLAG); - pm.getMatrix((float) endpoint, end, POSITION_MATRIX_FLAG); + pm.getMatrix((float) startPoint, start, POSITION_MATRIX_FLAG); + pm.getMatrix((float) midPoint, mid, POSITION_MATRIX_FLAG); + pm.getMatrix((float) endPoint, end, POSITION_MATRIX_FLAG); start.getValues(startPointMatrixData); end.getValues(endPointMatrixData); @@ -428,11 +590,11 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci Align the glyph vertically relative to the midpoint-on-the-path based on property alignment-baseline and any specified values for attribute ‘dy’ on a ‘tspan’ element. */ - mid.preTranslate((float) -halfway, (float) (dy - baselineShift)); - mid.preScale((float) scaledDirection, (float) scaledDirection); + mid.preTranslate((float) -halfWay, (float) (dy + baselineShift)); + mid.preScale((float) scaledDirection, (float) side); mid.postTranslate(0, (float) y); } else { - mid.setTranslate((float) startpoint, (float) (y + dy)); + mid.setTranslate((float) startPoint, (float) (y + dy)); } mid.preRotate((float) r); @@ -445,11 +607,14 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci return path; } - private double getRenderMethodScaling(double textMeasure, double distance) { - if (textPath != null && textPath.getMethod() == TextPathMethod.stretch) { - return distance / textMeasure; + private int getNumSpaces(int length, char[] chars) { + int numSpaces = 0; + for (int index = 0; index < length; index++) { + if (chars[index] == ' ') { + numSpaces++; + } } - return 1; + return numSpaces; } private double getAbsoluteStartOffset(String startOffset, double distance, double fontSize) { diff --git a/android/src/main/java/com/horcrux/svg/TextLengthAdjust.java b/android/src/main/java/com/horcrux/svg/TextLengthAdjust.java new file mode 100644 index 000000000..c3ed70f8e --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/TextLengthAdjust.java @@ -0,0 +1,7 @@ +package com.horcrux.svg; + +enum TextLengthAdjust +{ + spacing, + spacingAndGlyphs +} diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index dd9d7c775..c2acd0eea 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -24,6 +24,8 @@ */ class TextShadowNode extends GroupShadowNode { + String mTextLength = null; + TextLengthAdjust mLengthAdjust = TextLengthAdjust.spacing; AlignmentBaseline mAlignmentBaseline; private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; @@ -31,6 +33,18 @@ class TextShadowNode extends GroupShadowNode { private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaY; + @ReactProp(name = "textLength") + public void setmTextLength(@Nullable String length) { + mTextLength = length; + markUpdated(); + } + + @ReactProp(name = "lengthAdjust") + public void setLengthAdjust(@Nullable String adjustment) { + mLengthAdjust = TextLengthAdjust.valueOf(adjustment); + markUpdated(); + } + @ReactProp(name = "alignmentBaseline") public void setMethod(@Nullable String alignment) { mAlignmentBaseline = AlignmentBaseline.valueOf(alignment); From 3825bbd5c850ea4ecbd99f4d2d96b15a299c5934 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 29 Jul 2017 14:42:06 +0300 Subject: [PATCH 145/198] Fix startOfRendering calculation. Fix cursor position calculation. Refactor extractStroke strokeWidth handling. --- .../src/main/java/com/horcrux/svg/GlyphContext.java | 4 +--- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 13 +++++++------ lib/extract/extractStroke.js | 11 ++++++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 424081c2f..55a69784c 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -16,8 +16,6 @@ import javax.annotation.Nullable; -import static com.horcrux.svg.FontData.DEFAULT_FONT_SIZE; - // https://www.w3.org/TR/SVG/text.html#TSpanElement class GlyphContext { @@ -46,7 +44,7 @@ class GlyphContext { private final ArrayList mRsIndices = new ArrayList<>(); // Calculated on push context, percentage and em length depends on parent font size - private double mFontSize = DEFAULT_FONT_SIZE; + private double mFontSize = FontData.DEFAULT_FONT_SIZE; private FontData topFont = FontData.Defaults; // Current accumulated values diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 55412697e..7228d69f7 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -29,7 +29,6 @@ import static android.graphics.Matrix.MTRANS_Y; import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG; import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG; -import static com.horcrux.svg.TextPathMidLine.sharp; /** * Shadow node for virtual TSpan view @@ -146,7 +145,7 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { boolean sharpMidLine = false; final double fontSize = gc.getFontSize(); if (hasTextPath) { - sharpMidLine = textPath.getMidLine() == sharp; + sharpMidLine = textPath.getMidLine() == TextPathMidLine.sharp; /* Name side @@ -206,9 +205,10 @@ For the start (end) value, the text is rendered from the start (end) of the line a point on the path equal distance in both directions from the initial position on the path is reached. */ + final double absoluteStartOffset = getAbsoluteStartOffset(textPath.getStartOffset(), distance, fontSize); + offset += absoluteStartOffset; final double halfPathDistance = distance / 2; - offset += getAbsoluteStartOffset(textPath.getStartOffset(), distance, fontSize); - startOfRendering = -offset + (textAnchor == TextAnchor.middle ? -halfPathDistance : 0); + startOfRendering = absoluteStartOffset + (textAnchor == TextAnchor.middle ? -halfPathDistance : 0); endOfRendering = startOfRendering + distance; /* TextPathSpacing spacing = textPath.getSpacing(); @@ -494,9 +494,10 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci boolean isWordSeparator = currentChar == ' '; double wordSpace = isWordSeparator ? wordSpacing : 0; - double advance = charWidth + kerning + wordSpace + letterSpacing; + double spacing = wordSpace + letterSpacing; + double advance = charWidth + spacing; - double x = gc.nextX(advance); + double x = gc.nextX(kerning + advance); double y = gc.nextY(); double dx = gc.nextDeltaX(); double dy = gc.nextDeltaY(); diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 5c774e15c..13b412b9a 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -26,7 +26,7 @@ export default function(props, styleProperties) { }); const {stroke} = props; - const strokeWidth = props.strokeWidth; + let strokeWidth = props.strokeWidth; let strokeDasharray = props.strokeDasharray; if (!strokeDasharray || strokeDasharray === 'none') { @@ -43,14 +43,19 @@ export default function(props, styleProperties) { strokeDasharray.concat(strokeDasharray); } + if (!strokeWidth || typeof strokeWidth !== 'string') { + strokeWidth = `${strokeWidth || 1}`; + } + return { stroke: extractBrush(stroke), strokeOpacity: extractOpacity(props.strokeOpacity), strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeDasharray: strokeDasharray, - strokeWidth: `${strokeWidth || 1}`, + strokeWidth: strokeWidth, strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, - strokeMiterlimit: props.strokeMiterlimit || 4 + strokeMiterlimit: props.strokeMiterlimit || 4, }; + } From ab6bbd25e9e6ac3380eed12d2afe2dd423138178 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 29 Jul 2017 15:24:23 +0300 Subject: [PATCH 146/198] Implement extending the path beyond its end points with a straight line that is parallel to the tangent at the path at its end point. Fix lengthAdjust=spacing. --- .../java/com/horcrux/svg/TSpanShadowNode.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 7228d69f7..0a426c7be 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -273,7 +273,7 @@ For the start (end) value, the text is rendered from the start (end) of the line * */ double kerning = font.kerning; double wordSpacing = font.wordSpacing; - final double letterSpacing = font.letterSpacing; + double letterSpacing = font.letterSpacing; final boolean autoKerning = !font.manualKerning; /* @@ -327,8 +327,7 @@ A negative value is an error (see Error processing). switch (mLengthAdjust) { default: case spacing: - int numSpaces = getNumSpaces(length, chars); - wordSpacing += (author - textMeasure) / numSpaces; + letterSpacing += (author - textMeasure) / (length - 1); break; case spacingAndGlyphs: scaleSpacingAndGlyphs = author / textMeasure; @@ -544,12 +543,16 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci for a chrome/firefox/opera/safari compatible sharp text path rendering, which doesn't bend text smoothly along a right angle curve, (like Edge does) but keeps the mid-line orthogonal to the mid-point tangent at all times instead. + https://github.com/w3c/svgwg/issues/337 */ - if (startPoint < 0 || endPoint > distance || sharpMidLine) { + final int posAndTanFlags = POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG; + if (sharpMidLine) { + pm.getMatrix((float) midPoint, mid, posAndTanFlags); + } else { /* In the calculation above, if either the startpoint-on-the-path or the endpoint-on-the-path is off the end of the path, - TODO then extend the path beyond its end points with a straight line + then extend the path beyond its end points with a straight line that is parallel to the tangent at the path at its end point so that the midpoint-on-the-path can still be calculated. @@ -560,13 +563,23 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci or so that the line through the startpoint-on-the-path and the endpoint-on-the-path can still be calculated. + https://github.com/w3c/svgwg/issues/337#issuecomment-318056199 */ - final int flags = POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG; - pm.getMatrix((float) midPoint, mid, flags); - } else { - pm.getMatrix((float) startPoint, start, POSITION_MATRIX_FLAG); + if (startPoint < 0) { + pm.getMatrix(0, start, posAndTanFlags); + start.preTranslate((float) startPoint, 0); + } else { + pm.getMatrix((float) startPoint, start, POSITION_MATRIX_FLAG); + } + pm.getMatrix((float) midPoint, mid, POSITION_MATRIX_FLAG); - pm.getMatrix((float) endPoint, end, POSITION_MATRIX_FLAG); + + if (endPoint > distance) { + pm.getMatrix((float) distance, end, posAndTanFlags); + end.preTranslate((float) (endPoint - distance), 0); + } else { + pm.getMatrix((float) endPoint, end, POSITION_MATRIX_FLAG); + } start.getValues(startPointMatrixData); end.getValues(endPointMatrixData); @@ -608,16 +621,6 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci return path; } - private int getNumSpaces(int length, char[] chars) { - int numSpaces = 0; - for (int index = 0; index < length; index++) { - if (chars[index] == ' ') { - numSpaces++; - } - } - return numSpaces; - } - private double getAbsoluteStartOffset(String startOffset, double distance, double fontSize) { return PropHelper.fromRelative(startOffset, distance, 0, mScale, fontSize); } From 3005b592165e6a030233d68a5706102bbb8bf3be Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 29 Jul 2017 15:29:43 +0300 Subject: [PATCH 147/198] Add midLine prop/attr to TextPath.js --- elements/TextPath.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elements/TextPath.js b/elements/TextPath.js index a286ac1d1..ee82729dc 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -15,7 +15,7 @@ export default class extends Shape { static propTypes = textPathProps; render() { - let {children, href, startOffset, method, spacing, side, alignmentBaseline, ...props} = this.props; + let {children, href, startOffset, method, spacing, side, alignmentBaseline, midLine, ...props} = this.props; if (href) { let matched = href.match(idExpReg); @@ -23,7 +23,7 @@ export default class extends Shape { href = matched[1]; startOffset = `${startOffset || 0}`; return Date: Sat, 29 Jul 2017 16:48:31 +0300 Subject: [PATCH 148/198] Fix AlignmentBaseline --- .../java/com/horcrux/svg/TSpanShadowNode.java | 13 ++++++------ .../java/com/horcrux/svg/TextShadowNode.java | 20 ++++++++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 0a426c7be..83f153e38 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -368,10 +368,11 @@ A negative value is an error (see Error processing). final double descenderDepth = fm.descent; final double totalHeight = top + bottom; double baselineShift = 0; - if (mAlignmentBaseline != null) { + AlignmentBaseline baseline = getAlignmentBaseline(); + if (baseline != null) { // TODO alignment-baseline, test / verify behavior // TODO get per glyph baselines from font baseline table, for high-precision alignment - switch (mAlignmentBaseline) { + switch (baseline) { // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling default: case baseline: @@ -386,7 +387,7 @@ A negative value is an error (see Error processing). // Match the bottom of the box to the bottom of the parent’s content area. // text-after-edge = text-bottom // text-after-edge = descender depth - baselineShift = descenderDepth; + baselineShift = -descenderDepth; break; case alphabetic: @@ -398,7 +399,7 @@ A negative value is an error (see Error processing). case ideographic: // Match the box’s ideographic character face under-side baseline to that of its parent. // ideographic = descender depth - baselineShift = descenderDepth; + baselineShift = -descenderDepth; break; case middle: @@ -423,7 +424,7 @@ A negative value is an error (see Error processing). // There are no obvious formulas to calculate the position of these baselines. // At the time of writing FOP puts the hanging baseline at 80% of the ascender // height and the mathematical baseline at 50%. - baselineShift = ascenderHeight / 2; + baselineShift = 0.5 * ascenderHeight; break; case hanging: @@ -608,7 +609,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci mid.preScale((float) scaledDirection, (float) side); mid.postTranslate(0, (float) y); } else { - mid.setTranslate((float) startPoint, (float) (y + dy)); + mid.setTranslate((float) startPoint, (float) (y + dy + baselineShift)); } mid.preRotate((float) r); diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index c2acd0eea..0aef68ba4 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; import javax.annotation.Nullable; @@ -47,7 +48,7 @@ public void setLengthAdjust(@Nullable String adjustment) { @ReactProp(name = "alignmentBaseline") public void setMethod(@Nullable String alignment) { - mAlignmentBaseline = AlignmentBaseline.valueOf(alignment); + mAlignmentBaseline = AlignmentBaseline.getEnum(alignment); markUpdated(); } @@ -106,6 +107,23 @@ protected Path getPath(Canvas canvas, Paint paint) { return groupPath; } + AlignmentBaseline getAlignmentBaseline() { + if (mAlignmentBaseline == null) { + ReactShadowNode parent = this.getParent(); + while (parent != null) { + if (parent instanceof TextShadowNode) { + TextShadowNode node = (TextShadowNode)parent; + final AlignmentBaseline baseline = node.mAlignmentBaseline; + if (baseline != null) { + return baseline; + } + } + parent = parent.getParent(); + } + } + return mAlignmentBaseline; + } + void releaseCachedPath() { traverseChildren(new NodeRunnable() { public void run(VirtualNode node) { From 99875d2ad0212aa22b858fd721e2c1a57e385e8e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 29 Jul 2017 17:50:13 +0300 Subject: [PATCH 149/198] Fix last warnings. --- android/src/main/java/com/horcrux/svg/Brush.java | 4 +++- android/src/main/java/com/horcrux/svg/FontStyle.java | 2 +- android/src/main/java/com/horcrux/svg/GlyphPathBag.java | 6 +++--- android/src/main/java/com/horcrux/svg/SvgView.java | 4 ++++ android/src/main/java/com/horcrux/svg/SvgViewManager.java | 1 + .../src/main/java/com/horcrux/svg/TSpanShadowNode.java | 1 + .../main/java/com/horcrux/svg/TextLayoutAlgorithm.java | 2 ++ android/src/main/java/com/horcrux/svg/TextPathMethod.java | 2 +- .../src/main/java/com/horcrux/svg/TextPathMidLine.java | 2 +- .../src/main/java/com/horcrux/svg/TextPathShadowNode.java | 8 +++++--- android/src/main/java/com/horcrux/svg/TextPathSide.java | 2 +- .../src/main/java/com/horcrux/svg/TextPathSpacing.java | 2 +- android/src/main/java/com/horcrux/svg/TextShadowNode.java | 2 +- 13 files changed, 25 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/Brush.java b/android/src/main/java/com/horcrux/svg/Brush.java index 34301d2c2..437598285 100644 --- a/android/src/main/java/com/horcrux/svg/Brush.java +++ b/android/src/main/java/com/horcrux/svg/Brush.java @@ -37,11 +37,12 @@ class Brush { enum BrushType { LINEAR_GRADIENT(0), RADIAL_GRADIENT(1), - PATTERN(2); + @SuppressWarnings("unused")PATTERN(2); BrushType(int ni) { nativeInt = ni; } + @SuppressWarnings("unused") final int nativeInt; } @@ -51,6 +52,7 @@ enum BrushUnits { BrushUnits(int ni) { nativeInt = ni; } + @SuppressWarnings("unused") final int nativeInt; } diff --git a/android/src/main/java/com/horcrux/svg/FontStyle.java b/android/src/main/java/com/horcrux/svg/FontStyle.java index d17929337..cec356ebb 100644 --- a/android/src/main/java/com/horcrux/svg/FontStyle.java +++ b/android/src/main/java/com/horcrux/svg/FontStyle.java @@ -3,5 +3,5 @@ enum FontStyle { normal, italic, - oblique + @SuppressWarnings("unused")oblique } diff --git a/android/src/main/java/com/horcrux/svg/GlyphPathBag.java b/android/src/main/java/com/horcrux/svg/GlyphPathBag.java index 0b411dd30..5f111cd65 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphPathBag.java +++ b/android/src/main/java/com/horcrux/svg/GlyphPathBag.java @@ -6,9 +6,9 @@ import java.util.ArrayList; class GlyphPathBag { - private ArrayList paths = new ArrayList<>(); - private int[][] data = new int[255][]; - Paint paint; + private final ArrayList paths = new ArrayList<>(); + private final int[][] data = new int[255][]; + private final Paint paint; GlyphPathBag(Paint paint) { this.paint = paint; diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 1ff967f45..ab5e700f9 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -9,6 +9,7 @@ package com.horcrux.svg; +import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; @@ -29,12 +30,15 @@ /** * Custom {@link View} implementation that draws an RNSVGSvg React view and its \childrn. */ +@SuppressLint("ViewConstructor") public class SvgView extends View { public enum Events { + @SuppressWarnings("unused") EVENT_DATA_URL("onDataURL"); private final String mName; + @SuppressWarnings({"unused", "SameParameterValue"}) Events(final String name) { mName = name; } diff --git a/android/src/main/java/com/horcrux/svg/SvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java index 770b18e61..ec0236d61 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -51,6 +51,7 @@ static void setSvgView(SvgView svg) { mTagToSvgView.put(svg.getId(), svg); } + @SuppressWarnings("unused") static @Nullable SvgView getSvgViewByTag(int tag) { return mTagToSvgView.get(tag); } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 83f153e38..92febff72 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -87,6 +87,7 @@ protected Path getPath(Canvas canvas, Paint paint) { return mCache; } + @SuppressWarnings("ConstantConditions") private Path getLinePath(String line, Paint paint, Canvas canvas) { final int length = line.length(); final Path path = new Path(); diff --git a/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java b/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java index dea6cb4c0..b0de6d8bc 100644 --- a/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java +++ b/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java @@ -2,7 +2,9 @@ // TODO implement https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm +@SuppressWarnings("unused") public class TextLayoutAlgorithm { + @SuppressWarnings("EmptyMethod") void layoutText() { /* diff --git a/android/src/main/java/com/horcrux/svg/TextPathMethod.java b/android/src/main/java/com/horcrux/svg/TextPathMethod.java index dc1b82c17..d31edea8c 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathMethod.java +++ b/android/src/main/java/com/horcrux/svg/TextPathMethod.java @@ -2,5 +2,5 @@ enum TextPathMethod { align, - stretch + @SuppressWarnings("unused")stretch } diff --git a/android/src/main/java/com/horcrux/svg/TextPathMidLine.java b/android/src/main/java/com/horcrux/svg/TextPathMidLine.java index 764a64782..6f2e5864b 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathMidLine.java +++ b/android/src/main/java/com/horcrux/svg/TextPathMidLine.java @@ -8,5 +8,5 @@ */ enum TextPathMidLine { sharp, - smooth + @SuppressWarnings("unused")smooth } diff --git a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java index c43e67ec4..ade96c080 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextPathShadowNode.java @@ -65,19 +65,21 @@ public void setSharp(@Nullable String midLine) { markUpdated(); } + @SuppressWarnings("unused") TextPathMethod getMethod() { return mMethod; } - public TextPathSpacing getSpacing() { + @SuppressWarnings("unused") + TextPathSpacing getSpacing() { return mSpacing; } - public TextPathSide getSide() { + TextPathSide getSide() { return mSide; } - public TextPathMidLine getMidLine() { + TextPathMidLine getMidLine() { return mMidLine; } diff --git a/android/src/main/java/com/horcrux/svg/TextPathSide.java b/android/src/main/java/com/horcrux/svg/TextPathSide.java index 41348dcfc..e305e7821 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathSide.java +++ b/android/src/main/java/com/horcrux/svg/TextPathSide.java @@ -1,6 +1,6 @@ package com.horcrux.svg; enum TextPathSide { - left, + @SuppressWarnings("unused")left, right } diff --git a/android/src/main/java/com/horcrux/svg/TextPathSpacing.java b/android/src/main/java/com/horcrux/svg/TextPathSpacing.java index d1d0d9922..4af8b3e97 100644 --- a/android/src/main/java/com/horcrux/svg/TextPathSpacing.java +++ b/android/src/main/java/com/horcrux/svg/TextPathSpacing.java @@ -1,6 +1,6 @@ package com.horcrux.svg; enum TextPathSpacing { - auto, + @SuppressWarnings("unused")auto, exact } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 0aef68ba4..2d4dd2a7f 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -27,7 +27,7 @@ class TextShadowNode extends GroupShadowNode { String mTextLength = null; TextLengthAdjust mLengthAdjust = TextLengthAdjust.spacing; - AlignmentBaseline mAlignmentBaseline; + private AlignmentBaseline mAlignmentBaseline; private @Nullable ReadableArray mPositionX; private @Nullable ReadableArray mPositionY; private @Nullable ReadableArray mRotate; From 8892166ea3951e90531bb0533a723df8195f5494 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 30 Jul 2017 23:34:14 +0300 Subject: [PATCH 150/198] Fix non-/closed subpath start and end of rendering logic. --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 92febff72..8960ae4ce 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -98,10 +98,12 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { double distance = 0; PathMeasure pm = null; + boolean isClosed = false; final boolean hasTextPath = textPath != null; if (hasTextPath) { pm = new PathMeasure(textPath.getPath(), false); distance = pm.getLength(); + isClosed = pm.isClosed(); if (distance == 0) { return path; } @@ -141,10 +143,10 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { double offset = getTextAnchorOffset(textAnchor, textMeasure); int side = 1; - double endOfRendering = 0; double startOfRendering = 0; - boolean sharpMidLine = false; + double endOfRendering = distance; final double fontSize = gc.getFontSize(); + boolean sharpMidLine = false; if (hasTextPath) { sharpMidLine = textPath.getMidLine() == TextPathMidLine.sharp; /* @@ -208,9 +210,11 @@ For the start (end) value, the text is rendered from the start (end) of the line */ final double absoluteStartOffset = getAbsoluteStartOffset(textPath.getStartOffset(), distance, fontSize); offset += absoluteStartOffset; - final double halfPathDistance = distance / 2; - startOfRendering = absoluteStartOffset + (textAnchor == TextAnchor.middle ? -halfPathDistance : 0); - endOfRendering = startOfRendering + distance; + if (isClosed) { + final double halfPathDistance = distance / 2; + startOfRendering = absoluteStartOffset + (textAnchor == TextAnchor.middle ? -halfPathDistance : 0); + endOfRendering = startOfRendering + distance; + } /* TextPathSpacing spacing = textPath.getSpacing(); if (spacing == TextPathSpacing.auto) { From 54545aee6a219e9d41726e2f467f707ab37d8b58 Mon Sep 17 00:00:00 2001 From: magicismight Date: Thu, 3 Aug 2017 14:48:18 +0800 Subject: [PATCH 151/198] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 960dcee03..61809f0f0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.3.1", + "version": "5.3.2", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 2759db4749b68b0de87c4cd4aa6a227d7467b086 Mon Sep 17 00:00:00 2001 From: magicismight Date: Thu, 3 Aug 2017 15:03:29 +0800 Subject: [PATCH 152/198] Revert 0.47.0 --- android/src/main/java/com/horcrux/svg/SvgPackage.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 278ab13a7..716a8da3c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -44,6 +44,11 @@ public List createViewManagers(ReactApplicationContext reactContext new SvgViewManager()); } + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + @Override public List createNativeModules(ReactApplicationContext reactContext) { return Collections.singletonList(new SvgViewModule(reactContext)); From 3ebce7dae98af0af1be4a9dbcd75dce35d233b6c Mon Sep 17 00:00:00 2001 From: magicismight Date: Thu, 3 Aug 2017 15:04:23 +0800 Subject: [PATCH 153/198] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61809f0f0..22065228a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.3.2", + "version": "5.3.3", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 553c17794e2887a4e686b7ce9541c91da9dd9b50 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 3 Aug 2017 21:35:30 +0300 Subject: [PATCH 154/198] Implement support for multi-letter ligatures. Typographic ligature: In writing and typography, a ligature occurs where two or more graphemes or letters are joined as a single glyph. Optimize kerning and advance widths calculation using Paint.getTextWidths(String text, float[] widths); Make strokeWidth numberProp instead of string. Fix caching of AlignmentBaseline. Rename distance to pathLength. Upgrade gradle build tools. --- android/build.gradle | 2 +- .../java/com/horcrux/svg/TSpanShadowNode.java | 61 +++++++++++++------ .../java/com/horcrux/svg/TextShadowNode.java | 1 + lib/props.js | 2 +- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index cc8cda597..b1fa32561 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' } } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 8960ae4ce..00bbc94b2 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -19,6 +19,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; +import android.support.v4.graphics.PaintCompat; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -96,15 +97,15 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { return path; } - double distance = 0; + double pathLength = 0; PathMeasure pm = null; boolean isClosed = false; final boolean hasTextPath = textPath != null; if (hasTextPath) { pm = new PathMeasure(textPath.getPath(), false); - distance = pm.getLength(); + pathLength = pm.getLength(); isClosed = pm.isClosed(); - if (distance == 0) { + if (pathLength == 0) { return path; } } @@ -113,8 +114,10 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { FontData font = gc.getFont(); applyTextPropertiesToPaint(paint, font); GlyphPathBag bag = new GlyphPathBag(paint); + boolean[] ligature = new boolean[length]; final char[] chars = line.toCharArray(); - + float[] advances = new float[length]; + paint.getTextWidths(line, advances); /* Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ and property text-anchor. @@ -144,7 +147,7 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { int side = 1; double startOfRendering = 0; - double endOfRendering = distance; + double endOfRendering = pathLength; final double fontSize = gc.getFontSize(); boolean sharpMidLine = false; if (hasTextPath) { @@ -208,12 +211,12 @@ For the start (end) value, the text is rendered from the start (end) of the line a point on the path equal distance in both directions from the initial position on the path is reached. */ - final double absoluteStartOffset = getAbsoluteStartOffset(textPath.getStartOffset(), distance, fontSize); + final double absoluteStartOffset = getAbsoluteStartOffset(textPath.getStartOffset(), pathLength, fontSize); offset += absoluteStartOffset; if (isClosed) { - final double halfPathDistance = distance / 2; + final double halfPathDistance = pathLength / 2; startOfRendering = absoluteStartOffset + (textAnchor == TextAnchor.middle ? -halfPathDistance : 0); - endOfRendering = startOfRendering + distance; + endOfRendering = startOfRendering + pathLength; } /* TextPathSpacing spacing = textPath.getSpacing(); @@ -469,9 +472,11 @@ A negative value is an error (see Error processing). final float[] startPointMatrixData = new float[9]; final float[] endPointMatrixData = new float[9]; - String previous = ""; - double previousCharWidth = 0; for (int index = 0; index < length; index++) { + if (ligature[index]) { + // Skip rendering other grapheme clusters of ligatures (already rendered) + continue; + } char currentChar = chars[index]; String current = String.valueOf(currentChar); @@ -479,6 +484,21 @@ A negative value is an error (see Error processing). Determine the glyph's charwidth (i.e., the amount which the current text position advances horizontally when the glyph is drawn using horizontal text layout). */ + boolean hasLigature = false; + int nextIndex = index; + while (++nextIndex < length) { + float nextWidth = advances[nextIndex]; + if (nextWidth > 0) { + break; + } + String nextLigature = current + String.valueOf(chars[nextIndex]); + boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature); + if (hasNextLigature) { + ligature[nextIndex] = true; + current = nextLigature; + hasLigature = true; + } + } double charWidth = paint.measureText(current) * scaleSpacingAndGlyphs; /* @@ -491,10 +511,8 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci using the user agent's distance along the path algorithm. */ if (autoKerning) { - double bothCharsWidth = paint.measureText(previous + current) * scaleSpacingAndGlyphs; - kerning = bothCharsWidth - previousCharWidth - charWidth; - previousCharWidth = charWidth; - previous = current; + double kerned = advances[index] * scaleSpacingAndGlyphs; + kerning = kerned - charWidth; } boolean isWordSeparator = currentChar == ' '; @@ -580,9 +598,9 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci pm.getMatrix((float) midPoint, mid, POSITION_MATRIX_FLAG); - if (endPoint > distance) { - pm.getMatrix((float) distance, end, posAndTanFlags); - end.preTranslate((float) (endPoint - distance), 0); + if (endPoint > pathLength) { + pm.getMatrix((float) pathLength, end, posAndTanFlags); + end.preTranslate((float) (endPoint - pathLength), 0); } else { pm.getMatrix((float) endPoint, end, POSITION_MATRIX_FLAG); } @@ -619,7 +637,14 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci mid.preRotate((float) r); - Path glyph = bag.getOrCreateAndCache(currentChar, current); + + Path glyph; + if (hasLigature) { + glyph = new Path(); + paint.getTextPath(current, 0, current.length(), 0, 0, glyph); + } else { + glyph = bag.getOrCreateAndCache(currentChar, current); + } glyph.transform(mid); path.addPath(glyph); } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 2d4dd2a7f..df0cb77de 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -115,6 +115,7 @@ AlignmentBaseline getAlignmentBaseline() { TextShadowNode node = (TextShadowNode)parent; final AlignmentBaseline baseline = node.mAlignmentBaseline; if (baseline != null) { + mAlignmentBaseline = baseline; return baseline; } } diff --git a/lib/props.js b/lib/props.js index 51fda0034..7b01046a0 100644 --- a/lib/props.js +++ b/lib/props.js @@ -39,7 +39,7 @@ const definationProps = { const strokeProps = { stroke: PropTypes.string, - strokeWidth: PropTypes.string, + strokeWidth: numberProp, strokeOpacity: numberProp, strokeDasharray: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.string]), strokeDashoffset: numberProp, From b38ae08e9c58d4ea954795243080c4716925ca98 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 3 Aug 2017 21:44:36 +0300 Subject: [PATCH 155/198] Add com.android.support:appcompat-v7:25.3.1 to dependencies. Set SDK version to latest stable with available sources. --- android/build.gradle | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b1fa32561..175476ffc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,12 +11,12 @@ buildscript { apply plugin: 'com.android.library' android { - compileSdkVersion 26 - buildToolsVersion '26' + compileSdkVersion 25 + buildToolsVersion '25.0.3' defaultConfig { minSdkVersion 16 - targetSdkVersion 26 + targetSdkVersion 25 versionCode 1 versionName "1.0" } @@ -35,5 +35,7 @@ repositories { } dependencies { + compile "com.android.support:appcompat-v7:25.3.1" + //noinspection GradleDynamicVersion compile 'com.facebook.react:react-native:+' } From 786b5f237555faa3db478e614f054209904778b0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 00:26:00 +0300 Subject: [PATCH 156/198] 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 4482ea046..3eb1adedc 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 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 7e5b17f49..886bf5576 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 @@ public void setStrokeOpacity(float strokeOpacity) { @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 @@ private boolean setupStrokePaint(Paint paint, float opacity) { 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 000000000..610a9fc84 --- /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 13b412b9a..53bc40696 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 1f8cce52e..be8c3bfcf 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 7b01046a0..b66dba0ab 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']), From bc5435078672bf2ad5ff82d29349219c8250e6ef Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 05:01:30 +0300 Subject: [PATCH 157/198] Disable optional ligatures when letterSpacing != 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the effective letter-spacing between two characters is not zero (due to either justification or non-zero computed ‘letter-spacing’), user agents should not apply optional ligatures. https://www.w3.org/TR/css-text-3/#letter-spacing-property --- .../java/com/horcrux/svg/TSpanShadowNode.java | 69 ++++++++++++++----- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 00bbc94b2..a4a9007cf 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -118,6 +118,11 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { final char[] chars = line.toCharArray(); float[] advances = new float[length]; paint.getTextWidths(line, advances); + + /* + This would give both advances and textMeasure in one call / looping over the text + double textMeasure = paint.getTextRunAdvances(line, 0, length, 0, length, true, advances, 0); + */ /* Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ and property text-anchor. @@ -472,31 +477,45 @@ A negative value is an error (see Error processing). final float[] startPointMatrixData = new float[9]; final float[] endPointMatrixData = new float[9]; + String previous = ""; + double previousCharWidth = 0; + + /* + When the effective letter-spacing between two characters is not zero + (due to either justification or non-zero computed ‘letter-spacing’), + user agents should not apply optional ligatures. + https://www.w3.org/TR/css-text-3/#letter-spacing-property + */ + final boolean allowOptionalLigatures = letterSpacing == 0; + for (int index = 0; index < length; index++) { - if (ligature[index]) { - // Skip rendering other grapheme clusters of ligatures (already rendered) - continue; - } char currentChar = chars[index]; String current = String.valueOf(currentChar); + boolean alreadyRenderedGraphemeCluster = ligature[index]; /* Determine the glyph's charwidth (i.e., the amount which the current text position advances horizontally when the glyph is drawn using horizontal text layout). */ boolean hasLigature = false; - int nextIndex = index; - while (++nextIndex < length) { - float nextWidth = advances[nextIndex]; - if (nextWidth > 0) { - break; - } - String nextLigature = current + String.valueOf(chars[nextIndex]); - boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature); - if (hasNextLigature) { - ligature[nextIndex] = true; - current = nextLigature; - hasLigature = true; + if (allowOptionalLigatures) { + if (alreadyRenderedGraphemeCluster) { + current = ""; + } else { + int nextIndex = index; + while (++nextIndex < length) { + float nextWidth = advances[nextIndex]; + if (nextWidth > 0) { + break; + } + String nextLigature = current + String.valueOf(chars[nextIndex]); + boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature); + if (hasNextLigature) { + ligature[nextIndex] = true; + current = nextLigature; + hasLigature = true; + } + } } } double charWidth = paint.measureText(current) * scaleSpacingAndGlyphs; @@ -511,8 +530,16 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci using the user agent's distance along the path algorithm. */ if (autoKerning) { - double kerned = advances[index] * scaleSpacingAndGlyphs; - kerning = kerned - charWidth; + if (allowOptionalLigatures) { + double kerned = advances[index] * scaleSpacingAndGlyphs; + kerning = kerned - charWidth; + } else { + double bothCharsWidth = paint.measureText(previous + current) * scaleSpacingAndGlyphs; + double kerned = bothCharsWidth - previousCharWidth; + kerning = kerned - charWidth; + previousCharWidth = charWidth; + previous = current; + } } boolean isWordSeparator = currentChar == ' '; @@ -526,6 +553,12 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci double dy = gc.nextDeltaY(); double r = gc.nextRotation(); + if (alreadyRenderedGraphemeCluster) { + // Skip rendering other grapheme clusters of ligatures (already rendered), + // But, make sure to increment index positions by making gc.next() calls. + continue; + } + advance = advance * side; charWidth = charWidth * side; double cursor = offset + (x + dx) * side; From 2939584d1b98cf1ab8940e184e051151939b1925 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 14:10:02 +0300 Subject: [PATCH 158/198] Implement font-variant-ligatures. https://developer.mozilla.org/en/docs/Web/CSS/font-variant-ligatures https://www.w3.org/TR/css-fonts-3/#font-variant-ligatures-prop --- .../main/java/com/horcrux/svg/FontData.java | 4 ++ .../java/com/horcrux/svg/TSpanShadowNode.java | 65 ++++++++++++++++--- lib/attributes.js | 3 +- lib/extract/extractText.js | 2 + lib/props.js | 24 +++++++ 5 files changed, 89 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index 7c10bb117..b6bd15bd6 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -19,12 +19,14 @@ class FontData { private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; private static final String TEXT_DECORATION = "textDecoration"; + private static final String FONT_VARIANT_LIGATURES = "fontVariantLigatures"; final double fontSize; final String fontFamily; final FontStyle fontStyle; final FontWeight fontWeight; + final FontVariantLigatures fontVariantLigatures; final TextAnchor textAnchor; final TextDecoration textDecoration; @@ -41,6 +43,7 @@ private FontData() { fontFamily = ""; fontStyle = FontStyle.normal; fontWeight = FontWeight.Normal; + fontVariantLigatures = FontVariantLigatures.normal; textAnchor = TextAnchor.start; textDecoration = TextDecoration.None; @@ -81,6 +84,7 @@ private double toAbsolute(String string, double scale, double fontSize) { fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily; fontStyle = font.hasKey(FONT_STYLE) ? FontStyle.valueOf(font.getString(FONT_STYLE)) : parent.fontStyle; fontWeight = font.hasKey(FONT_WEIGHT) ? FontWeight.getEnum(font.getString(FONT_WEIGHT)) : parent.fontWeight; + fontVariantLigatures = font.hasKey(FONT_VARIANT_LIGATURES) ? FontVariantLigatures.valueOf(font.getString(FONT_VARIANT_LIGATURES)) : parent.fontVariantLigatures; textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor; textDecoration = font.hasKey(TEXT_DECORATION) ? TextDecoration.getEnum(font.getString(TEXT_DECORATION)) : parent.textDecoration; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index a4a9007cf..0150c14a8 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -289,6 +289,63 @@ For the start (end) value, the text is rendered from the start (end) of the line double letterSpacing = font.letterSpacing; final boolean autoKerning = !font.manualKerning; + /* + 11.1.2. Fonts and glyphs + + A font consists of a collection of glyphs together with other information (collectively, + the font tables) necessary to use those glyphs to present characters on some visual medium. + + The combination of the collection of glyphs and the font tables is called the font data. + + A font may supply substitution and positioning tables that can be used by a formatter + (text shaper) to re-order, combine and position a sequence of glyphs to form one or more + composite glyphs. + + The combining may be as simple as a ligature, or as complex as an indic syllable which + combines, usually with some re-ordering, multiple consonants and vowel glyphs. + + The tables may be language dependent, allowing the use of language appropriate letter forms. + + When a glyph, simple or composite, represents an indivisible unit for typesetting purposes, + it is know as a typographic character. + + Ligatures are an important feature of advance text layout. Some ligatures are discretionary + (TODO) while others (e.g. in Arabic) are required. + + The following explicit rules apply to ligature formation: + + Ligature formation should not be enabled when characters are in different DOM text nodes; + thus, characters separated by markup should not use ligatures. + + Ligature formation should not be enabled when characters are in different text chunks. + + Discretionary ligatures should not be used when the spacing between two characters is not + the same as the default space (e.g. when letter-spacing has a non-default value, + or text-align has a value of justify and text-justify has a value of distribute). + (See CSS Text Module Level 3, ([css-text-3]). + + SVG attributes such as ‘dx’, ‘textLength’, and ‘spacing’ (in ‘textPath’) that may reposition + typographic characters do not break discretionary ligatures. + + If discretionary ligatures are not desired + they can be turned off by using the font-variant-ligatures property. + + /* + When the effective letter-spacing between two characters is not zero + (due to either justification or non-zero computed ‘letter-spacing’), + user agents should not apply optional ligatures. + https://www.w3.org/TR/css-text-3/#letter-spacing-property + */ + final boolean allowOptionalLigatures = letterSpacing == 0 && + font.fontVariantLigatures == FontVariantLigatures.normal; + + /* + For OpenType fonts, discretionary ligatures include those enabled by + the liga, clig, dlig, hlig, and cala features; + TODO required ligatures are found in the rlig feature. + https://svgwg.org/svg2-draft/text.html#FontsGlyphs + */ + /* Name Value Initial value Animatable textLength | | See below yes @@ -480,14 +537,6 @@ A negative value is an error (see Error processing). String previous = ""; double previousCharWidth = 0; - /* - When the effective letter-spacing between two characters is not zero - (due to either justification or non-zero computed ‘letter-spacing’), - user agents should not apply optional ligatures. - https://www.w3.org/TR/css-text-3/#letter-spacing-property - */ - final boolean allowOptionalLigatures = letterSpacing == 0; - for (int index = 0; index < length; index++) { char currentChar = chars[index]; String current = String.valueOf(currentChar); diff --git a/lib/attributes.js b/lib/attributes.js index 9865a0691..51d42f42d 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -30,7 +30,8 @@ function fontDiffer(a, b) { a.textDecoration !== b.textDecoration || a.letterSpacing !== b.letterSpacing || a.wordSpacing !== b.wordSpacing || - a.kerning !== b.kerning + a.kerning !== b.kerning || + a.fontVariantLigatures !== b.fontVariantLigatures ); } diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index be8c3bfcf..10ce6f20e 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -54,6 +54,7 @@ export function extractFont(props) { letterSpacing, wordSpacing, kerning, + fontVariantLigatures, } = props; let { fontSize, @@ -76,6 +77,7 @@ export function extractFont(props) { letterSpacing, wordSpacing, kerning, + fontVariantLigatures, }, prop => !_.isNil(prop)); if (typeof font === 'string') { diff --git a/lib/props.js b/lib/props.js index b66dba0ab..89da6ddc7 100644 --- a/lib/props.js +++ b/lib/props.js @@ -142,6 +142,29 @@ const wordSpacing = PropTypes.string; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning const kerning = PropTypes.string; +/* +Name: font-variant-ligatures +Value: normal | none | [ || || || ] + Initial: normal + Applies to: all elements + Inherited: yes + Percentages: N/A + Media: visual + Computed value: as specified + Animatable: no + + Ligatures and contextual forms are ways of combining glyphs to produce more harmonized forms. + + = [ common-ligatures | no-common-ligatures ] + = [ discretionary-ligatures | no-discretionary-ligatures ] + = [ historical-ligatures | no-historical-ligatures ] + = [ contextual | no-contextual ] + + https://developer.mozilla.org/en/docs/Web/CSS/font-variant-ligatures + https://www.w3.org/TR/css-fonts-3/#font-variant-ligatures-prop +*/ +const fontVariantLigatures = PropTypes.oneOf(['normal', 'none']); + const fontProps = { fontStyle, fontVariant, @@ -154,6 +177,7 @@ const fontProps = { letterSpacing, wordSpacing, kerning, + fontVariantLigatures, font }; From fe7e8b2b65badb78d3d69d4c689cf6a7092b0e27 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 16:29:06 +0300 Subject: [PATCH 159/198] Implement baselineShift and verticalAlign (preferred). Transverse Box Alignment, specifies how an inline-level box is aligned within the line, and by how much the box is shifted up from its alignment point. https://drafts.csswg.org/css-inline/#propdef-vertical-align https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift https://www.w3.org/TR/css-inline-3/#transverse-alignment --- .../java/com/horcrux/svg/TSpanShadowNode.java | 54 +++++++++++++++++++ .../java/com/horcrux/svg/TextShadowNode.java | 50 +++++++++++++++++ lib/attributes.js | 2 + lib/extract/extractText.js | 4 ++ lib/props.js | 45 ++++++++++++++++ 5 files changed, 155 insertions(+) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 0150c14a8..744c7d545 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -438,6 +438,7 @@ A negative value is an error (see Error processing). final double descenderDepth = fm.descent; final double totalHeight = top + bottom; double baselineShift = 0; + String baselineShiftString = getBaselineShift(); AlignmentBaseline baseline = getAlignmentBaseline(); if (baseline != null) { // TODO alignment-baseline, test / verify behavior @@ -525,6 +526,59 @@ A negative value is an error (see Error processing). baselineShift = top; break; } + /* + 2.2.2. Alignment Shift: baseline-shift longhand + + This property specifies by how much the box is shifted up from its alignment point. + It does not apply when alignment-baseline is top or bottom. + + Authors should use the vertical-align shorthand instead of this property. + + Values have the following meanings: + + + Raise (positive value) or lower (negative value) by the specified length. + + Raise (positive value) or lower (negative value) by the specified percentage of the line-height. + TODO sub + Lower by the offset appropriate for subscripts of the parent’s box. + (The UA should use the parent’s font data to find this offset whenever possible.) + TODO super + Raise by the offset appropriate for superscripts of the parent’s box. + (The UA should use the parent’s font data to find this offset whenever possible.) + + User agents may additionally support the keyword baseline as computing to 0 + if is necessary for them to support legacy SVG content. + Issue: We would prefer to remove this, + and are looking for feedback from SVG user agents as to whether it’s necessary. + + https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift + */ + if (baselineShiftString != null) { + switch (baseline) { + case top: + case bottom: + break; + + default: + switch (baselineShiftString) { + case "sub": + // TODO + break; + + case "super": + // TODO + break; + + case "baseline": + break; + + default: + baselineShift -= PropHelper.fromRelative(baselineShiftString, fontSize, 0, mScale, fontSize); + } + break; + } + } } final Matrix start = new Matrix(); diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index df0cb77de..aa539667d 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -26,6 +26,7 @@ class TextShadowNode extends GroupShadowNode { String mTextLength = null; + String mBaselineShift = null; TextLengthAdjust mLengthAdjust = TextLengthAdjust.spacing; private AlignmentBaseline mAlignmentBaseline; private @Nullable ReadableArray mPositionX; @@ -52,6 +53,34 @@ public void setMethod(@Nullable String alignment) { markUpdated(); } + @ReactProp(name = "baselineShift") + public void setBaselineShift(@Nullable String baselineShift) { + mBaselineShift = baselineShift; + markUpdated(); + } + + @ReactProp(name = "verticalAlign") + public void setVerticalAlign(@Nullable String verticalAlign) { + if (verticalAlign != null) { + verticalAlign = verticalAlign.trim(); + int i = verticalAlign.lastIndexOf(' '); + try { + mAlignmentBaseline = AlignmentBaseline.getEnum(verticalAlign.substring(i)); + } catch (IllegalArgumentException e) { + mAlignmentBaseline = AlignmentBaseline.baseline; + } + try { + mBaselineShift = verticalAlign.substring(0, i); + } catch (IndexOutOfBoundsException e) { + mBaselineShift = null; + } + } else { + mAlignmentBaseline = AlignmentBaseline.baseline; + mBaselineShift = null; + } + markUpdated(); + } + @ReactProp(name = "rotate") public void setRotate(@Nullable ReadableArray rotate) { mRotate = rotate; @@ -122,9 +151,30 @@ AlignmentBaseline getAlignmentBaseline() { parent = parent.getParent(); } } + if (mAlignmentBaseline == null) { + mAlignmentBaseline = AlignmentBaseline.baseline; + } return mAlignmentBaseline; } + String getBaselineShift() { + if (mBaselineShift == null) { + ReactShadowNode parent = this.getParent(); + while (parent != null) { + if (parent instanceof TextShadowNode) { + TextShadowNode node = (TextShadowNode)parent; + final String baselineShift = node.mBaselineShift; + if (baselineShift != null) { + mBaselineShift = baselineShift; + return baselineShift; + } + } + parent = parent.getParent(); + } + } + return mBaselineShift; + } + void releaseCachedPath() { traverseChildren(new NodeRunnable() { public void run(VirtualNode node) { diff --git a/lib/attributes.js b/lib/attributes.js index 51d42f42d..bdbf6db2e 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -112,6 +112,8 @@ const PathAttributes = { const TextSpecificAttributes = { ...RenderableAttributes, alignmentBaseline: true, + baselineShift: true, + verticalAlign: true, lengthAdjust: true, textLength: true, }; diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 10ce6f20e..df75ea2f1 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -94,6 +94,8 @@ export default function(props, container) { dx, dy, alignmentBaseline, + baselineShift, + verticalAlign, } = props; let { rotate, @@ -137,5 +139,7 @@ export default function(props, container) { deltaX, deltaY, alignmentBaseline, + baselineShift, + verticalAlign, }; } diff --git a/lib/props.js b/lib/props.js index 89da6ddc7..7e69fab9a 100644 --- a/lib/props.js +++ b/lib/props.js @@ -197,6 +197,29 @@ const lengthAdjust = PropTypes.oneOf(['spacing', 'spacingAndGlyphs']); */ const textLength = PropTypes.string; +/* + 2.2. Transverse Box Alignment: the vertical-align property + + Name: vertical-align + Value: <‘baseline-shift’> || <‘alignment-baseline’> + Initial: baseline + Applies to: inline-level boxes + Inherited: no + Percentages: N/A + Media: visual + Computed value: as specified + Canonical order: per grammar + Animation type: discrete + This shorthand property specifies how an inline-level box is aligned within the line. + Values are the same as for its longhand properties, see below. + + Authors should use this property (vertical-align) instead of its longhands. + + https://www.w3.org/TR/css-inline-3/#transverse-alignment + https://drafts.csswg.org/css-inline/#propdef-vertical-align + */ +const verticalAlign = PropTypes.string; + /* Name: alignment-baseline @@ -217,10 +240,32 @@ const textLength = PropTypes.string; */ const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabetic', 'ideographic', 'middle', 'central', 'mathematical', 'text-top', 'bottom', 'center', 'top', 'text-before-edge', 'text-after-edge', 'before-edge', 'after-edge', 'hanging']); +/* + 2.2.2. Alignment Shift: baseline-shift longhand + + Name: baseline-shift + Value: | | sub | super + Initial: 0 + Applies to: inline-level boxes + Inherited: no + Percentages: refer to the used value of line-height + Media: visual + Computed value: absolute length, percentage, or keyword specified + Animation type: discrete + + This property specifies by how much the box is shifted up from its alignment point. + It does not apply when alignment-baseline is top or bottom. + + https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift +*/ +const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), PropTypes.string]); + const textSpecificProps = { ...pathProps, ...fontProps, alignmentBaseline, + baselineShift, + verticalAlign, lengthAdjust, textLength, }; From 81a832a11598af60550114cd4a5c3518d4ff7a3f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 17:10:07 +0300 Subject: [PATCH 160/198] Add missing FontVariantLigatures.java enum file. --- .../src/main/java/com/horcrux/svg/FontVariantLigatures.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 android/src/main/java/com/horcrux/svg/FontVariantLigatures.java diff --git a/android/src/main/java/com/horcrux/svg/FontVariantLigatures.java b/android/src/main/java/com/horcrux/svg/FontVariantLigatures.java new file mode 100644 index 000000000..32b232f58 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FontVariantLigatures.java @@ -0,0 +1,6 @@ +package com.horcrux.svg; + +enum FontVariantLigatures { + normal, + none +} From 1ffd195bc2bd76f37503ce578578b76f6eddf8a3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 17:43:34 +0300 Subject: [PATCH 161/198] Remove duplicate createJSModules. --- android/src/main/java/com/horcrux/svg/SvgPackage.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 12c9a0ebc..5aaaa9a40 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -44,11 +44,6 @@ public List createViewManagers(ReactApplicationContext reactContext new SvgViewManager()); } - @Override - public List> createJSModules() { - return Collections.emptyList(); - } - @Override public List createNativeModules(ReactApplicationContext reactContext) { return Collections.singletonList(new SvgViewModule(reactContext)); From a97122a9942d18a76baee80a5cf30bcd46b22188 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 4 Aug 2017 21:17:02 +0300 Subject: [PATCH 162/198] Initial support for OpenType.js font data/tables/metrics. --- android/src/main/java/com/horcrux/svg/FontData.java | 6 +++++- android/src/main/java/com/horcrux/svg/TSpanShadowNode.java | 4 ++++ lib/attributes.js | 3 ++- lib/extract/extractText.js | 2 ++ lib/props.js | 1 + 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index b6bd15bd6..8103d7dc6 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -15,6 +15,7 @@ class FontData { private static final double DEFAULT_LETTER_SPACING = 0d; private static final String KERNING = "kerning"; + private static final String FONT_DATA = "fontData"; private static final String TEXT_ANCHOR = "textAnchor"; private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; @@ -22,9 +23,9 @@ class FontData { private static final String FONT_VARIANT_LIGATURES = "fontVariantLigatures"; final double fontSize; - final String fontFamily; final FontStyle fontStyle; + final ReadableMap fontData; final FontWeight fontWeight; final FontVariantLigatures fontVariantLigatures; @@ -40,6 +41,7 @@ class FontData { static final FontData Defaults = new FontData(); private FontData() { + fontData = null; fontFamily = ""; fontStyle = FontStyle.normal; fontWeight = FontWeight.Normal; @@ -81,6 +83,8 @@ private double toAbsolute(String string, double scale, double fontSize) { fontSize = parentFontSize; } + fontData = font.hasKey(FONT_DATA) ? font.getMap(FONT_DATA) : parent.fontData; + fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily; fontStyle = font.hasKey(FONT_STYLE) ? FontStyle.valueOf(font.getString(FONT_STYLE)) : parent.fontStyle; fontWeight = font.hasKey(FONT_WEIGHT) ? FontWeight.getEnum(font.getString(FONT_WEIGHT)) : parent.fontWeight; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 744c7d545..cd08310dc 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -21,6 +21,7 @@ import android.graphics.Typeface; import android.support.v4.graphics.PaintCompat; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -346,6 +347,9 @@ the same as the default space (e.g. when letter-spacing has a non-default value, https://svgwg.org/svg2-draft/text.html#FontsGlyphs */ + // OpenType.js font data + ReadableMap fontData = font.fontData; + /* Name Value Initial value Animatable textLength | | See below yes diff --git a/lib/attributes.js b/lib/attributes.js index bdbf6db2e..d32f2b6df 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -31,7 +31,8 @@ function fontDiffer(a, b) { a.letterSpacing !== b.letterSpacing || a.wordSpacing !== b.wordSpacing || a.kerning !== b.kerning || - a.fontVariantLigatures !== b.fontVariantLigatures + a.fontVariantLigatures !== b.fontVariantLigatures || + a.fontData !== b.fontData ); } diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index df75ea2f1..019a97ab9 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -45,6 +45,7 @@ function parseFontString(font) { export function extractFont(props) { const { + fontData, fontStyle, fontVariant, fontWeight, @@ -66,6 +67,7 @@ export function extractFont(props) { fontSize = fontSize ? '' + fontSize : null; const ownedFont = _.pickBy({ + fontData, fontStyle, fontVariant, fontWeight, diff --git a/lib/props.js b/lib/props.js index 7e69fab9a..8705a1934 100644 --- a/lib/props.js +++ b/lib/props.js @@ -268,6 +268,7 @@ const textSpecificProps = { verticalAlign, lengthAdjust, textLength, + fontData: PropTypes.object, }; // https://svgwg.org/svg2-draft/text.html#TSpanAttributes From f603e7191cc5e3b3bc4ed4c4318c69e653ca0566 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 5 Aug 2017 01:18:16 +0300 Subject: [PATCH 163/198] Use setFontFeatureSettings for disabling all discretionary ligatures while keeping required ligatures (e.g. in Arabic) --- .../java/com/horcrux/svg/TSpanShadowNode.java | 201 ++++++++++-------- 1 file changed, 117 insertions(+), 84 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index cd08310dc..85cb44328 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -19,6 +19,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; +import android.os.Build; import android.support.v4.graphics.PaintCompat; import com.facebook.react.bridge.ReadableMap; @@ -117,6 +118,122 @@ private Path getLinePath(String line, Paint paint, Canvas canvas) { GlyphPathBag bag = new GlyphPathBag(paint); boolean[] ligature = new boolean[length]; final char[] chars = line.toCharArray(); + + /* + * + * Three properties affect the space between characters and words: + * + * ‘kerning’ indicates whether the user agent should adjust inter-glyph spacing + * based on kerning tables that are included in the relevant font + * (i.e., enable auto-kerning) or instead disable auto-kerning + * and instead set inter-character spacing to a specific length (typically, zero). + * + * ‘letter-spacing’ indicates an amount of space that is to be added between text + * characters supplemental to any spacing due to the ‘kerning’ property. + * + * ‘word-spacing’ indicates the spacing behavior between words. + * + * Letter-spacing is applied after bidi reordering and is in addition to any word-spacing. + * Depending on the justification rules in effect, user agents may further increase + * or decrease the space between typographic character units in order to justify text. + * + * */ + double kerning = font.kerning; + double wordSpacing = font.wordSpacing; + double letterSpacing = font.letterSpacing; + final boolean autoKerning = !font.manualKerning; + + /* + 11.1.2. Fonts and glyphs + + A font consists of a collection of glyphs together with other information (collectively, + the font tables) necessary to use those glyphs to present characters on some visual medium. + + The combination of the collection of glyphs and the font tables is called the font data. + + A font may supply substitution and positioning tables that can be used by a formatter + (text shaper) to re-order, combine and position a sequence of glyphs to form one or more + composite glyphs. + + The combining may be as simple as a ligature, or as complex as an indic syllable which + combines, usually with some re-ordering, multiple consonants and vowel glyphs. + + The tables may be language dependent, allowing the use of language appropriate letter forms. + + When a glyph, simple or composite, represents an indivisible unit for typesetting purposes, + it is know as a typographic character. + + Ligatures are an important feature of advance text layout. Some ligatures are discretionary + while others (e.g. in Arabic) are required. + + The following explicit rules apply to ligature formation: + + Ligature formation should not be enabled when characters are in different DOM text nodes; + thus, characters separated by markup should not use ligatures. + + Ligature formation should not be enabled when characters are in different text chunks. + + Discretionary ligatures should not be used when the spacing between two characters is not + the same as the default space (e.g. when letter-spacing has a non-default value, + or text-align has a value of justify and text-justify has a value of distribute). + (See CSS Text Module Level 3, ([css-text-3]). + + SVG attributes such as ‘dx’, ‘textLength’, and ‘spacing’ (in ‘textPath’) that may reposition + typographic characters do not break discretionary ligatures. + + If discretionary ligatures are not desired + they can be turned off by using the font-variant-ligatures property. + + /* + When the effective letter-spacing between two characters is not zero + (due to either justification or non-zero computed ‘letter-spacing’), + user agents should not apply optional ligatures. + https://www.w3.org/TR/css-text-3/#letter-spacing-property + */ + final boolean allowOptionalLigatures = letterSpacing == 0 && + font.fontVariantLigatures == FontVariantLigatures.normal; + + /* + For OpenType fonts, discretionary ligatures include those enabled by + the liga, clig, dlig, hlig, and cala features; + required ligatures are found in the rlig feature. + https://svgwg.org/svg2-draft/text.html#FontsGlyphs + + http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings + + https://www.microsoft.com/typography/otspec/featurelist.htm + https://www.microsoft.com/typography/otspec/featuretags.htm + https://www.microsoft.com/typography/otspec/features_pt.htm + https://www.microsoft.com/typography/otfntdev/arabicot/features.aspx + http://unifraktur.sourceforge.net/testcases/enable_opentype_features/ + https://en.wikipedia.org/wiki/List_of_typographic_features + http://ilovetypography.com/OpenType/opentype-features.html + https://www.typotheque.com/articles/opentype_features_in_css + https://practice.typekit.com/lesson/caring-about-opentype-features/ + http://stateofwebtype.com/ + + 6.12. Low-level font feature settings control: the font-feature-settings property + + Name: font-feature-settings + Value: normal | # + Initial: normal + Applies to: all elements + Inherited: yes + Percentages: N/A + Media: visual + Computed value: as specified + Animatable: no + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (allowOptionalLigatures) { + paint.setFontFeatureSettings("'kern', 'liga', 'clig', 'dlig', 'hlig', 'cala', 'rlig'"); + } else { + paint.setFontFeatureSettings("'kern', 'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, 'rlig'"); + } + } + // OpenType.js font data + ReadableMap fontData = font.fontData; + float[] advances = new float[length]; paint.getTextWidths(line, advances); @@ -266,90 +383,6 @@ For the start (end) value, the text is rendered from the start (end) of the line TODO implement stretch */ - /* - * - * Three properties affect the space between characters and words: - * - * ‘kerning’ indicates whether the user agent should adjust inter-glyph spacing - * based on kerning tables that are included in the relevant font - * (i.e., enable auto-kerning) or instead disable auto-kerning - * and instead set inter-character spacing to a specific length (typically, zero). - * - * ‘letter-spacing’ indicates an amount of space that is to be added between text - * characters supplemental to any spacing due to the ‘kerning’ property. - * - * ‘word-spacing’ indicates the spacing behavior between words. - * - * Letter-spacing is applied after bidi reordering and is in addition to any word-spacing. - * Depending on the justification rules in effect, user agents may further increase - * or decrease the space between typographic character units in order to justify text. - * - * */ - double kerning = font.kerning; - double wordSpacing = font.wordSpacing; - double letterSpacing = font.letterSpacing; - final boolean autoKerning = !font.manualKerning; - - /* - 11.1.2. Fonts and glyphs - - A font consists of a collection of glyphs together with other information (collectively, - the font tables) necessary to use those glyphs to present characters on some visual medium. - - The combination of the collection of glyphs and the font tables is called the font data. - - A font may supply substitution and positioning tables that can be used by a formatter - (text shaper) to re-order, combine and position a sequence of glyphs to form one or more - composite glyphs. - - The combining may be as simple as a ligature, or as complex as an indic syllable which - combines, usually with some re-ordering, multiple consonants and vowel glyphs. - - The tables may be language dependent, allowing the use of language appropriate letter forms. - - When a glyph, simple or composite, represents an indivisible unit for typesetting purposes, - it is know as a typographic character. - - Ligatures are an important feature of advance text layout. Some ligatures are discretionary - (TODO) while others (e.g. in Arabic) are required. - - The following explicit rules apply to ligature formation: - - Ligature formation should not be enabled when characters are in different DOM text nodes; - thus, characters separated by markup should not use ligatures. - - Ligature formation should not be enabled when characters are in different text chunks. - - Discretionary ligatures should not be used when the spacing between two characters is not - the same as the default space (e.g. when letter-spacing has a non-default value, - or text-align has a value of justify and text-justify has a value of distribute). - (See CSS Text Module Level 3, ([css-text-3]). - - SVG attributes such as ‘dx’, ‘textLength’, and ‘spacing’ (in ‘textPath’) that may reposition - typographic characters do not break discretionary ligatures. - - If discretionary ligatures are not desired - they can be turned off by using the font-variant-ligatures property. - - /* - When the effective letter-spacing between two characters is not zero - (due to either justification or non-zero computed ‘letter-spacing’), - user agents should not apply optional ligatures. - https://www.w3.org/TR/css-text-3/#letter-spacing-property - */ - final boolean allowOptionalLigatures = letterSpacing == 0 && - font.fontVariantLigatures == FontVariantLigatures.normal; - - /* - For OpenType fonts, discretionary ligatures include those enabled by - the liga, clig, dlig, hlig, and cala features; - TODO required ligatures are found in the rlig feature. - https://svgwg.org/svg2-draft/text.html#FontsGlyphs - */ - - // OpenType.js font data - ReadableMap fontData = font.fontData; - /* Name Value Initial value Animatable textLength | | See below yes From a1b57d3f4f039ce0cd4b61175788eaec31e0a5e8 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 7 Aug 2017 18:26:45 +0300 Subject: [PATCH 164/198] Implement default and required css 3 fonts features, fontFeatureSettings, sub-/super- script baseline-shift. Activate OpenType localized forms and features required for proper display of composed characters and marks. --- .../main/java/com/horcrux/svg/FontData.java | 4 + .../java/com/horcrux/svg/TSpanShadowNode.java | 106 ++++++++++++------ lib/attributes.js | 3 +- lib/extract/extractText.js | 2 + lib/props.js | 101 +++++++++++++++++ 5 files changed, 181 insertions(+), 35 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java index 8103d7dc6..f2df64455 100644 --- a/android/src/main/java/com/horcrux/svg/FontData.java +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -20,6 +20,7 @@ class FontData { private static final String WORD_SPACING = "wordSpacing"; private static final String LETTER_SPACING = "letterSpacing"; private static final String TEXT_DECORATION = "textDecoration"; + private static final String FONT_FEATURE_SETTINGS = "fontFeatureSettings"; private static final String FONT_VARIANT_LIGATURES = "fontVariantLigatures"; final double fontSize; @@ -27,6 +28,7 @@ class FontData { final FontStyle fontStyle; final ReadableMap fontData; final FontWeight fontWeight; + final String fontFeatureSettings; final FontVariantLigatures fontVariantLigatures; final TextAnchor textAnchor; @@ -45,6 +47,7 @@ private FontData() { fontFamily = ""; fontStyle = FontStyle.normal; fontWeight = FontWeight.Normal; + fontFeatureSettings = ""; fontVariantLigatures = FontVariantLigatures.normal; textAnchor = TextAnchor.start; @@ -88,6 +91,7 @@ private double toAbsolute(String string, double scale, double fontSize) { fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily; fontStyle = font.hasKey(FONT_STYLE) ? FontStyle.valueOf(font.getString(FONT_STYLE)) : parent.fontStyle; fontWeight = font.hasKey(FONT_WEIGHT) ? FontWeight.getEnum(font.getString(FONT_WEIGHT)) : parent.fontWeight; + fontFeatureSettings = font.hasKey(FONT_FEATURE_SETTINGS) ? font.getString(FONT_FEATURE_SETTINGS) : parent.fontFeatureSettings; fontVariantLigatures = font.hasKey(FONT_VARIANT_LIGATURES) ? FontVariantLigatures.valueOf(font.getString(FONT_VARIANT_LIGATURES)) : parent.fontVariantLigatures; textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor; diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 85cb44328..4752faea6 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -223,12 +223,41 @@ the same as the default space (e.g. when letter-spacing has a non-default value, Media: visual Computed value: as specified Animatable: no + + https://drafts.csswg.org/css-fonts-3/#default-features + + 7.1. Default features + + For OpenType fonts, user agents must enable the default features defined in the OpenType + documentation for a given script and writing mode. + + Required ligatures, common ligatures and contextual forms must be enabled by default + (OpenType features: rlig, liga, clig, calt), + along with localized forms (OpenType feature: locl), + and features required for proper display of composed characters and marks + (OpenType features: ccmp, mark, mkmk). + + These features must always be enabled, even when the value of the ‘font-variant’ and + ‘font-feature-settings’ properties is ‘normal’. + + Individual features are only disabled when explicitly overridden by the author, + as when ‘font-variant-ligatures’ is set to ‘no-common-ligatures’. + + TODO For handling complex scripts such as Arabic, Mongolian or Devanagari additional features + are required. + + TODO For upright text within vertical text runs, + vertical alternates (OpenType feature: vert) must be enabled. */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + String required = "'rlig', 'liga', 'clig', 'calt', 'locl', 'ccmp', 'mark', 'mkmk',"; + String defaultFeatures = required + "'kern', "; if (allowOptionalLigatures) { - paint.setFontFeatureSettings("'kern', 'liga', 'clig', 'dlig', 'hlig', 'cala', 'rlig'"); + String additionalLigatures = "'hlig', 'cala', "; + paint.setFontFeatureSettings(defaultFeatures + additionalLigatures + font.fontFeatureSettings); } else { - paint.setFontFeatureSettings("'kern', 'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, 'rlig'"); + String disableDiscretionaryLigatures = "'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, "; + paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings); } } // OpenType.js font data @@ -601,10 +630,32 @@ A negative value is an error (see Error processing). switch (baselineShiftString) { case "sub": // TODO + if (fontData != null && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) { + int unitsPerEm = fontData.getInt("unitsPerEm"); + ReadableMap tables = fontData.getMap("tables"); + if (tables.hasKey("os2")) { + ReadableMap os2 = tables.getMap("os2"); + if (os2.hasKey("ySubscriptYOffset")) { + double subOffset = os2.getDouble("ySubscriptYOffset"); + baselineShift += fontSize * subOffset / unitsPerEm; + } + } + } break; case "super": // TODO + if (fontData != null && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) { + int unitsPerEm = fontData.getInt("unitsPerEm"); + ReadableMap tables = fontData.getMap("tables"); + if (tables.hasKey("os2")) { + ReadableMap os2 = tables.getMap("os2"); + if (os2.hasKey("ySuperscriptYOffset")) { + double superOffset = os2.getDouble("ySuperscriptYOffset"); + baselineShift -= fontSize * superOffset / unitsPerEm; + } + } + } break; case "baseline": @@ -625,9 +676,6 @@ A negative value is an error (see Error processing). final float[] startPointMatrixData = new float[9]; final float[] endPointMatrixData = new float[9]; - String previous = ""; - double previousCharWidth = 0; - for (int index = 0; index < length; index++) { char currentChar = chars[index]; String current = String.valueOf(currentChar); @@ -638,23 +686,21 @@ A negative value is an error (see Error processing). advances horizontally when the glyph is drawn using horizontal text layout). */ boolean hasLigature = false; - if (allowOptionalLigatures) { - if (alreadyRenderedGraphemeCluster) { - current = ""; - } else { - int nextIndex = index; - while (++nextIndex < length) { - float nextWidth = advances[nextIndex]; - if (nextWidth > 0) { - break; - } - String nextLigature = current + String.valueOf(chars[nextIndex]); - boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature); - if (hasNextLigature) { - ligature[nextIndex] = true; - current = nextLigature; - hasLigature = true; - } + if (alreadyRenderedGraphemeCluster) { + current = ""; + } else { + int nextIndex = index; + while (++nextIndex < length) { + float nextWidth = advances[nextIndex]; + if (nextWidth > 0) { + break; + } + String nextLigature = current + String.valueOf(chars[nextIndex]); + boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature); + if (hasNextLigature) { + ligature[nextIndex] = true; + current = nextLigature; + hasLigature = true; } } } @@ -670,16 +716,8 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci using the user agent's distance along the path algorithm. */ if (autoKerning) { - if (allowOptionalLigatures) { - double kerned = advances[index] * scaleSpacingAndGlyphs; - kerning = kerned - charWidth; - } else { - double bothCharsWidth = paint.measureText(previous + current) * scaleSpacingAndGlyphs; - double kerned = bothCharsWidth - previousCharWidth; - kerning = kerned - charWidth; - previousCharWidth = charWidth; - previous = current; - } + double kerned = advances[index] * scaleSpacingAndGlyphs; + kerning = kerned - charWidth; } boolean isWordSeparator = currentChar == ' '; @@ -699,8 +737,8 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci continue; } - advance = advance * side; - charWidth = charWidth * side; + advance *= side; + charWidth *= side; double cursor = offset + (x + dx) * side; double startPoint = cursor - advance; diff --git a/lib/attributes.js b/lib/attributes.js index d32f2b6df..2164ab512 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -32,7 +32,8 @@ function fontDiffer(a, b) { a.wordSpacing !== b.wordSpacing || a.kerning !== b.kerning || a.fontVariantLigatures !== b.fontVariantLigatures || - a.fontData !== b.fontData + a.fontData !== b.fontData || + a.fontFeatureSettings !== b.fontFeatureSettings ); } diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 019a97ab9..1df21b13c 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -56,6 +56,7 @@ export function extractFont(props) { wordSpacing, kerning, fontVariantLigatures, + fontFeatureSettings, } = props; let { fontSize, @@ -80,6 +81,7 @@ export function extractFont(props) { wordSpacing, kerning, fontVariantLigatures, + fontFeatureSettings, }, prop => !_.isNil(prop)); if (typeof font === 'string') { diff --git a/lib/props.js b/lib/props.js index 8705a1934..282fa3495 100644 --- a/lib/props.js +++ b/lib/props.js @@ -260,6 +260,106 @@ const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabeti */ const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), PropTypes.string]); +/* + 6.12. Low-level font feature settings control: the font-feature-settings property + + Name: font-feature-settings + Value: normal | # + Initial: normal + Applies to: all elements + Inherited: yes + Percentages: N/A + Media: visual + Computed value: as specified + Animatable: no + + This property provides low-level control over OpenType font features. + + It is intended as a way of providing access to font features + that are not widely used but are needed for a particular use case. + + Authors should generally use ‘font-variant’ and its related subproperties + whenever possible and only use this property for special cases where its use + is the only way of accessing a particular infrequently used font feature. + + enable small caps and use second swash alternate + font-feature-settings: "smcp", "swsh" 2; + A value of ‘normal’ means that no change in glyph selection or positioning occurs due to this property. + + Feature tag values have the following syntax: + + = [ | on | off ]? + The is a case-sensitive OpenType feature tag. As specified in the OpenType specification, + feature tags contain four ASCII characters. + + Tag strings longer or shorter than four characters, + or containing characters outside the U+20–7E codepoint range are invalid. + + Feature tags need only match a feature tag defined in the font, + so they are not limited to explicitly registered OpenType features. + + Fonts defining custom feature tags should follow the tag name rules + defined in the OpenType specification [OPENTYPE-FEATURES]. + + Feature tags not present in the font are ignored; + a user agent must not attempt to synthesize fallback behavior based on these feature tags. + + The one exception is that user agents may synthetically support the kern feature with fonts + that contain kerning data in the form of a ‘kern’ table but lack kern feature support in the ‘GPOS’ table. + + In general, authors should use the ‘font-kerning’ property to explicitly enable or disable kerning + since this property always affects fonts with either type of kerning data. + + If present, a value indicates an index used for glyph selection. + + An value must be 0 or greater. + + A value of 0 indicates that the feature is disabled. + + For boolean features, a value of 1 enables the feature. + + For non-boolean features, a value of 1 or greater enables the feature and indicates the feature selection index. + + A value of ‘on’ is synonymous with 1 and ‘off’ is synonymous with 0. + + If the value is omitted, a value of 1 is assumed. + + font-feature-settings: "dlig" 1; /* dlig=1 enable discretionary ligatures * / + font-feature-settings: "smcp" on; /* smcp=1 enable small caps * / + font-feature-settings: 'c2sc'; /* c2sc=1 enable caps to small caps * / + font-feature-settings: "liga" off; /* liga=0 no common ligatures * / + font-feature-settings: "tnum", 'hist'; /* tnum=1, hist=1 enable tabular numbers and historical forms * / + font-feature-settings: "tnum" "hist"; /* invalid, need a comma-delimited list * / + font-feature-settings: "silly" off; /* invalid, tag too long * / + font-feature-settings: "PKRN"; /* PKRN=1 enable custom feature * / + font-feature-settings: dlig; /* invalid, tag must be a string * / + + When values greater than the range supported by the font are specified, the behavior is explicitly undefined. + + For boolean features, in general these will enable the feature. + + For non-boolean features, out of range values will in general be equivalent to a 0 value. + + However, in both cases the exact behavior will depend upon the way the font is designed + (specifically, which type of lookup is used to define the feature). + + Although specifically defined for OpenType feature tags, + feature tags for other modern font formats that support font features may be added in the future. + + Where possible, features defined for other font formats + should attempt to follow the pattern of registered OpenType tags. + + The Japanese text below will be rendered with half-width kana characters: + + body { font-feature-settings: "hwid"; /* Half-width OpenType feature * / } + +

毎日カレー食べてるのに、飽きない

+ + https://drafts.csswg.org/css-fonts-3/#propdef-font-feature-settings + https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings +*/ +const fontFeatureSettings = PropTypes.string; + const textSpecificProps = { ...pathProps, ...fontProps, @@ -269,6 +369,7 @@ const textSpecificProps = { lengthAdjust, textLength, fontData: PropTypes.object, + fontFeatureSettings, }; // https://svgwg.org/svg2-draft/text.html#TSpanAttributes From 7f91c7cc2be2bb81b1b557da7c0d04f82775110b Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Mon, 7 Aug 2017 19:29:54 +0300 Subject: [PATCH 165/198] DOCFIX (cherry picked from commit 7a76285) --- .../main/java/com/horcrux/svg/TSpanShadowNode.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 4752faea6..e75060a27 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -163,8 +163,9 @@ A font consists of a collection of glyphs together with other information (colle When a glyph, simple or composite, represents an indivisible unit for typesetting purposes, it is know as a typographic character. - Ligatures are an important feature of advance text layout. Some ligatures are discretionary - while others (e.g. in Arabic) are required. + Ligatures are an important feature of advance text layout. + + Some ligatures are discretionary while others (e.g. in Arabic) are required. The following explicit rules apply to ligature formation: @@ -446,8 +447,8 @@ For the start (end) value, the text is rendered from the start (end) of the line A negative value is an error (see Error processing). The ‘textLength’ attribute is only applied when the wrapping area is not defined by the - shape-inside or the inline-size properties. It is also not applied for any ‘text’ or - ‘tspan’ element that has forced line breaks (due to a white-space value of pre or + TODO shape-inside or the inline-size properties. It is also not applied for any ‘text’ or + TODO ‘tspan’ element that has forced line breaks (due to a white-space value of pre or pre-line). If the attribute is not specified anywhere within a ‘text’ element, the effect is as if @@ -824,9 +825,7 @@ and properties, including spacing properties (e.g. letter-spacing and word-spaci double endX = endPointMatrixData[MTRANS_X]; double endY = endPointMatrixData[MTRANS_Y]; - /* - line through the startpoint-on-the-path and the endpoint-on-the-path - */ + // line through the startpoint-on-the-path and the endpoint-on-the-path double lineX = endX - startX; double lineY = endY - startY; From 058183c0de054b7ae14de5da2a1bd59e8803cda5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 8 Aug 2017 03:04:46 +0300 Subject: [PATCH 166/198] Start https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm To implement support for bidirectional and vertical text etc. --- .../main/java/com/horcrux/svg/Direction.java | 6 + .../java/com/horcrux/svg/TSpanShadowNode.java | 2 +- .../com/horcrux/svg/TextLayoutAlgorithm.java | 2077 ++++++++++------- 3 files changed, 1213 insertions(+), 872 deletions(-) create mode 100644 android/src/main/java/com/horcrux/svg/Direction.java diff --git a/android/src/main/java/com/horcrux/svg/Direction.java b/android/src/main/java/com/horcrux/svg/Direction.java new file mode 100644 index 000000000..3cc0ce72f --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/Direction.java @@ -0,0 +1,6 @@ +package com.horcrux.svg; + +enum Direction { + ltr, + rtl +} diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index e75060a27..59448cbff 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -45,7 +45,7 @@ class TSpanShadowNode extends TextShadowNode { private static final String TTF = ".ttf"; private Path mCache; - private @Nullable String mContent; + @Nullable String mContent; private TextPathShadowNode textPath; @ReactProp(name = "content") diff --git a/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java b/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java index b0de6d8bc..178df4239 100644 --- a/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java +++ b/android/src/main/java/com/horcrux/svg/TextLayoutAlgorithm.java @@ -2,964 +2,1299 @@ // TODO implement https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm -@SuppressWarnings("unused") -public class TextLayoutAlgorithm { - @SuppressWarnings("EmptyMethod") - void layoutText() { -/* - - Setup - - - Let root be the result of generating - typographic character positions for the - ‘text’ element and its subtree, laid out as if it - were an absolutely positioned element. - - This will be a single line of text unless the - white-space property causes line breaks. +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.PointF; +import com.facebook.react.uimanager.ReactShadowNode; +import java.util.ArrayList; - Let count be the number of DOM characters - within the ‘text’ element's subtree. - - - Let result be an array of length count - whose entries contain the per-character information described - above. Each entry is initialized as follows: - - its global index number equal to its position in the array, - its "x" coordinate set to "unspecified", - its "y" coordinate set to "unspecified", - its "rotate" coordinate set to "unspecified", - its "hidden" flag is false, - its "addressable" flag is true, - its "middle" flag is false, - its "anchored chunk" flag is false. - - If result is empty, then return result. - - - Let CSS_positions be an array of length - count whose entries will be filled with the - x and y positions of the corresponding - typographic character in root. The array - entries are initialized to (0, 0). +@SuppressWarnings("unused") +public class TextLayoutAlgorithm { + class CharacterInformation { + int index; + double x = 0; + double y = 0; + double advance; + char character; + double rotate = 0; + TextShadowNode element; + boolean hidden = false; + boolean middle = false; + boolean resolved = false; + boolean xSpecified = false; + boolean ySpecified = false; + boolean addressable = true; + boolean anchoredChunk = false; + boolean rotateSpecified = false; + boolean firstCharacterInResolvedDescendant = false; + + CharacterInformation(int index, char c) { + this.index = index; + this.character = c; + } + } + class LayoutInput { + TextShadowNode text; + boolean horizontal; + } - Let "horizontal" be a flag, true if the writing mode of ‘text’ - is horizontal, false otherwise. + private void getSubTreeTypographicCharacterPositions( + ArrayList inTextPath, + ArrayList subtree, + StringBuilder line, + ReactShadowNode node, + TextPathShadowNode textPath + ) { + if (node instanceof TSpanShadowNode) { + final TSpanShadowNode tSpanShadowNode = (TSpanShadowNode) node; + String content = tSpanShadowNode.mContent; + if (content == null) { + for (int i = 0; i < node.getChildCount(); i++) { + getSubTreeTypographicCharacterPositions(inTextPath, subtree, line, node.getChildAt(i), textPath); + } + } else { + for (int i = 0; i < content.length(); i++) { + subtree.add(tSpanShadowNode); + inTextPath.add(textPath); + } + line.append(content); + } + } else { + textPath = node instanceof TextPathShadowNode ? (TextPathShadowNode) node : textPath; + for (int i = 0; i < node.getChildCount(); i++) { + getSubTreeTypographicCharacterPositions(inTextPath, subtree, line, node.getChildAt(i), textPath); + } + } + } + CharacterInformation[] layoutText(LayoutInput layoutInput) { +/* + Setup + Let root be the result of generating + typographic character positions for the + ‘text’ element and its subtree, laid out as if it + were an absolutely positioned element. + + This will be a single line of text unless the + white-space property causes line breaks. +*/ + TextShadowNode text = layoutInput.text; + StringBuilder line = new StringBuilder(); + ArrayList subtree = new ArrayList<>(); + ArrayList inTextPath = new ArrayList<>(); + getSubTreeTypographicCharacterPositions(inTextPath, subtree, line, text, null); + final char[] root = line.toString().toCharArray(); +/* + Let count be the number of DOM characters + within the ‘text’ element's subtree. +*/ + int count = root.length; +/* + Let result be an array of length count + whose entries contain the per-character information described + above. Each entry is initialized as follows: + + its global index number equal to its position in the array, + its "x" coordinate set to "unspecified", + its "y" coordinate set to "unspecified", + its "rotate" coordinate set to "unspecified", + its "hidden" flag is false, + its "addressable" flag is true, + its "middle" flag is false, + its "anchored chunk" flag is false. +*/ + final CharacterInformation[] result = new CharacterInformation[count]; + for (int i = 0; i < count; i++) { + result[i] = new CharacterInformation(i, root[i]); + } +/* + If result is empty, then return result. +*/ + if (count == 0) { + return result; + } +/* + Let CSS_positions be an array of length + count whose entries will be filled with the + x and y positions of the corresponding + typographic character in root. The array + entries are initialized to (0, 0). +*/ + PointF[] CSS_positions = new PointF[count]; + for (int i = 0; i < count; i++) { + CSS_positions[i] = new PointF(0, 0); + } +/* + Let "horizontal" be a flag, true if the writing mode of ‘text’ + is horizontal, false otherwise. +*/ + final boolean horizontal = true; +/* Set flags and assign initial positions - For each array element with index i in - result: - - - - Set addressable to false if the character at index i was: - + For each array element with index i in + result: +*/ + for (int i = 0; i < count; i++) { +/* + TODO Set addressable to false if the character at index i was: part of the text content of a non-rendered element + discarded during layout due to being a + collapsed + white space character, a soft hyphen character, or a + bidi control character; or - discarded during layout due to being a - collapsed - white space character, a soft hyphen character, or a - bidi control character; or + discarded during layout due to being a + collapsed + segment break; or - discarded during layout due to being a - collapsed - segment break; or + trimmed + from the start or end of a line. - trimmed - from the start or end of a line. + Since there is collapsible white space not addressable by glyph + positioning attributes in the following ‘text’ element + (with a standard font), the "B" glyph will be placed at x=300. + + A + B + + This is because the white space before the "A", and all but one white space + character between the "A" and "B", is collapsed away or trimmed. +*/ + result[i].addressable = true; +/* - Since there is collapsible white space not addressable by glyph - positioning attributes in the following ‘text’ element - (with a standard font), the "B" glyph will be placed at x=300. - - - A - B - - - This is because the white space before the "A", and all but one white space - character between the "A" and "B", is collapsed away or trimmed. - - - - - Set middle to true if the character at index i - is the second or later character that corresponds to a typographic character. - - - If the character at index i corresponds to a typographic character at the beginning of a line, then set the "anchored - chunk" flag of result[i] to true. - - This ensures chunks shifted by text-anchor do not - span multiple lines. - - - - If addressable is true and middle is false then - set CSS_positions[i] to the position of the - corresponding typographic character as determined by the CSS - renderer. Otherwise, if i > 0, then set - CSS_positions[i] = - CSS_positions[i − 1] + Set middle to true if the character at index i + TODO is the second or later character that corresponds to a typographic character. +*/ + result[i].middle = false; +/* + TODO If the character at index i corresponds to a typographic character at the beginning of a line, then set the "anchored + chunk" flag of result[i] to true. + This ensures chunks shifted by text-anchor do not + span multiple lines. +*/ + result[i].anchoredChunk = i == 0; +/* + If addressable is true and middle is false then + set CSS_positions[i] to the position of the + TODO corresponding typographic character as determined by the CSS + renderer. Otherwise, if i > 0, then set + CSS_positions[i] = + CSS_positions[i − 1] + +*/ + if (result[i].addressable && !result[i].middle) { + CSS_positions[i].set(0, 0); + } else if (i > 0) { + CSS_positions[i].set(CSS_positions[i - 1]); + } + } +/* Resolve character positioning - Position adjustments (e.g values in a ‘x’ attribute) - specified by a node apply to all characters in that node including - characters in the node's descendants. Adjustments specified in - descendant nodes, however, override adjustments from ancestor - nodes. This section resolves which adjustments are to be applied to - which characters. It also directly sets the rotate coordinate - of result. - - - - Set up: - - - Let resolve_x, resolve_y, - resolve_dx, and resolve_dy be arrays of - length count whose entries are all initialized - to "unspecified". - - - Set "in_text_path" flag false. - - This flag will allow ‘y’ (‘x’) - attribute values to be ignored for horizontal (vertical) - text inside ‘textPath’ elements. - - - - Call the following procedure with the ‘text’ element node. - - - - - Procedure: resolve character - positioning: - - A recursive procedure that takes as input a node and - whose steps are as follows: - - - - If node is a ‘text’ or ‘tspan’ node: - - - Let index equal the "global index number" of the - first character in the node. - - - Let x, y, dx, dy - and rotate be the lists of values from the - corresponding attributes on node, or empty - lists if the corresponding attribute was not specified - or was invalid. - - - If "in_text_path" flag is false: - - - Let new_chunk_count - = max(length of x, length of y). - - - Else: - - - If the "horizontal" flag is true: - - - Let new_chunk_count = length of x. - - - - - Else: - - - Let new_chunk_count = length of y. - - - - - - - Let length be the number of DOM characters in the - subtree rooted at node. - - - Let i = 0 and j = 0. - - i is an index of addressable characters in the node; - j is an index of all characters in the node. - - - - While j < length, do: - - This loop applies the ‘x’, ‘y’, - ‘dx’, ‘dy’ and ‘rotate’ - attributes to the content inside node. - - - - If the "addressable" flag of result[index + - j] is true, then: - - - If i < new_check_count, then - set the "anchored chunk" flag of - result[index + j] to - true. Else set the flag to false. - - Setting the flag to false ensures that ‘x’ - and ‘y’ attributes set in a ‘text’ - element don't create anchored chunk in a ‘textPath’ - element when they should not. - - - - If i < length of x, - then set resolve_x[index - + j] to x[i]. - - - If "in_text_path" flag is true and the "horizontal" - flag is false, unset - resolve_x[index]. - - The ‘x’ attribute is ignored for - vertical text on a path. - - - - If i < length of y, - then set resolve_y[index - + j] to y[i]. - - - If "in_text_path" flag is true and the "horizontal" - flag is true, unset - resolve_y[index]. - - The ‘y’ attribute is ignored for - horizontal text on a path. - - - - If i < length of dx, - then set resolve_dx[index - + j] to dy[i]. - - - If i < length of dy, - then set resolve_dy[index - + j] to dy[i]. - - - If i < length of rotate, - then set the angle value of result[index - + j] to rotate[i]. - Otherwise, if rotate is not empty, then - set result[index + j] - to result[index + j − 1]. - - - Set i = i + 1. - - - - - Set j = j + 1. - - - - - - - If node is a ‘textPath’ node: - - - Let index equal the global index number of the - first character in the node (including descendant nodes). - - - Set the "anchored chunk" flag of result[index] - to true. - - A ‘textPath’ element always creates an anchored chunk. - - + Position adjustments (e.g values in a ‘x’ attribute) + specified by a node apply to all characters in that node including + characters in the node's descendants. Adjustments specified in + descendant nodes, however, override adjustments from ancestor + nodes. This section resolves which adjustments are to be applied to + which characters. It also directly sets the rotate coordinate + of result. + + Set up: + + Let resolve_x, resolve_y, + resolve_dx, and resolve_dy be arrays of + length count whose entries are all initialized + to "unspecified". +*/ + String[] resolve_x = new String[count]; + String[] resolve_y = new String[count]; + String[] resolve_dx = new String[count]; + String[] resolve_dy = new String[count]; +/* - Set in_text_path flag true. + Set "in_text_path" flag false. + This flag will allow ‘y’ (‘x’) + attribute values to be ignored for horizontal (vertical) + text inside ‘textPath’ elements. +*/ + boolean in_text_path = false; +/* + Call the following procedure with the ‘text’ element node. + + Procedure: resolve character + positioning: + + A recursive procedure that takes as input a node and + whose steps are as follows: +*/ + class CharacterPositioningResolver { + private int global = 0; + private boolean horizontal = true; + private boolean in_text_path = false; + private CharacterInformation[] result; + private String[] resolve_x; + private String[] resolve_y; + private String[] resolve_dx; + private String[] resolve_dy; + + private CharacterPositioningResolver( + CharacterInformation[] result, + String[] resolve_x, + String[] resolve_y, + String[] resolve_dx, + String[] resolve_dy + ) { + this.result = result; + this.resolve_x = resolve_x; + this.resolve_y = resolve_y; + this.resolve_dx = resolve_dx; + this.resolve_dy = resolve_dy; + } + + private void resolveCharacterPositioning(TextShadowNode node) { +/* + If node is a ‘text’ or ‘tspan’ node: +*/ + if (node.getClass() == TextShadowNode.class || node.getClass() == TSpanShadowNode.class) { +/* + Let index equal the "global index number" of the + first character in the node. +*/ + int index = global; +/* + Let x, y, dx, dy + and rotate be the lists of values from the + TODO corresponding attributes on node, or empty + lists if the corresponding attribute was not specified + or was invalid. +*/ + // https://www.w3.org/TR/SVG/text.html#TSpanElementXAttribute + String[] x = new String[]{}; + // https://www.w3.org/TR/SVG/text.html#TSpanElementYAttribute + String[] y = new String[]{}; + // Current SVGLengthList + // https://www.w3.org/TR/SVG/types.html#DataTypeLengths - For each child node child of node: + // https://www.w3.org/TR/SVG/text.html#TSpanElementDXAttribute + String[] dx = new String[]{}; + // https://www.w3.org/TR/SVG/text.html#TSpanElementDYAttribute + String[] dy = new String[]{}; - Resolve glyph - positioning of child. + // Current SVGLengthList + // https://www.w3.org/TR/SVG/types.html#DataTypeNumbers + // https://www.w3.org/TR/SVG/text.html#TSpanElementRotateAttribute + double[] rotate = new double[]{}; +/* + If "in_text_path" flag is false: + Let new_chunk_count + = max(length of x, length of y). +*/ + int new_chunk_count; + if (!in_text_path) { + new_chunk_count = Math.max(x.length, y.length); +/* + Else: +*/ + } else { +/* + If the "horizontal" flag is true: - If node is a ‘textPath’ node: + Let new_chunk_count = length of x. +*/ + if (horizontal) { + new_chunk_count = x.length; +/* + Else: - Set "in_text_path" flag false. + Let new_chunk_count = length of y. +*/ + } else { + new_chunk_count = y.length; + } + } +/* + Let length be the number of DOM characters in the + subtree rooted at node. +*/ + String content = ((TSpanShadowNode) node).mContent; + int length = content == null ? 0 : content.length(); +/* + Let i = 0 and j = 0. + i is an index of addressable characters in the node; + j is an index of all characters in the node. +*/ + int i = 0; + int j = 0; +/* + While j < length, do: +*/ + while (j < length) { +/* + This loop applies the ‘x’, ‘y’, + ‘dx’, ‘dy’ and ‘rotate’ + attributes to the content inside node. + If the "addressable" flag of result[index + + j] is true, then: +*/ + if (result[index + j].addressable) { +/* + If i < TODO new_check_count, then (typo) + set the "anchored chunk" flag of + result[index + j] to + true. Else set the flag to false. + + Setting the flag to false ensures that ‘x’ + and ‘y’ attributes set in a ‘text’ + element don't create anchored chunk in a ‘textPath’ + element when they should not. +*/ + result[index + j].anchoredChunk = i < new_chunk_count; +/* + If i < length of x, + then set resolve_x[index + + j] to x[i]. +*/ + if (i < x.length) { + resolve_x[index + j] = x[i]; + } +/* + If "in_text_path" flag is true and the "horizontal" + flag is false, unset + resolve_x[index]. + The ‘x’ attribute is ignored for + vertical text on a path. +*/ + if (in_text_path && !horizontal) { + resolve_x[index] = ""; + } +/* + If i < length of y, + then set resolve_y[index + + j] to y[i]. +*/ + if (i < y.length) { + resolve_y[index + j] = y[i]; + } +/* + If "in_text_path" flag is true and the "horizontal" + flag is true, unset + resolve_y[index]. + + The ‘y’ attribute is ignored for + horizontal text on a path. +*/ + if (in_text_path && horizontal) { + resolve_y[index] = ""; + } +/* + If i < length of dx, + then set resolve_dx[index + + j] to TODO dy[i]. (typo) +*/ + if (i < dx.length) { + resolve_dx[index + j] = dx[i]; + } +/* + If i < length of dy, + then set resolve_dy[index + + j] to dy[i]. +*/ + if (i < dy.length) { + resolve_dy[index + j] = dy[i]; + } +/* + If i < length of rotate, + then set the angle value of result[index + + j] to rotate[i]. + Otherwise, if rotate is not empty, then + set result[index + j] + to result[index + j − 1]. +*/ + if (i < rotate.length) { + result[index + j].rotate = rotate[i]; + } else if (rotate.length != 0) { + result[index + j].rotate = result[index + j - 1].rotate; + } +/* + Set i = i + 1. + Set j = j + 1. +*/ + } + i++; + j++; + } +/* + If node is a ‘textPath’ node: + Let index equal the global index number of the + first character in the node (including descendant nodes). +*/ + } else if (node.getClass() == TextPathShadowNode.class) { + int index = global; +/* + Set the "anchored chunk" flag of result[index] + to true. + A ‘textPath’ element always creates an anchored chunk. +*/ + result[index].anchoredChunk = true; +/* + Set in_text_path flag true. +*/ + in_text_path = true; +/* + For each child node child of node: + Resolve glyph + positioning of child. +*/ + for (int child = 0; child < node.getChildCount(); child++) { + resolveCharacterPositioning((TextShadowNode) node.getChildAt(child)); + } +/* + If node is a ‘textPath’ node: + + Set "in_text_path" flag false. + +*/ + if (node instanceof TextPathShadowNode) { + in_text_path = false; + } + } + } + } + + CharacterPositioningResolver resolver = new CharacterPositioningResolver( + result, + resolve_x, + resolve_y, + resolve_dx, + resolve_dy + ); +/* Adjust positions: dx, dy - The ‘dx’ and ‘dy’ adjustments are applied - before adjustments due to the ‘textLength’ attribute while - the ‘x’, ‘y’ and ‘rotate’ - adjustments are applied after. - - - - Let shift be the cumulative x and - y shifts due to ‘x’ and ‘y’ - attributes, initialized to (0,0). - - - For each array element with index i in result: - - - If resolve_x[i] is unspecified, set it to 0. - If resolve_y[i] is unspecified, set it to 0. - - - Let shift.x = shift.x + resolve_x[i] - and shift.y = shift.y + resolve_y[i]. - - - Let result[i].x = CSS_positions[i].x + shift.x - and result[i].y = CSS_positions[i].y + shift.y. - - - - - - - Apply ‘textLength’ attribute - - - Set up: - - - Define resolved descendant node as a - descendant of node with a valid ‘textLength’ - attribute that is not itself a descendant node of a - descendant node that has a valid ‘textLength’ - attribute. - - - Call the following procedure with the ‘text’ element - node. - - - - - Procedure: resolve text length: - - A recursive procedure that takes as input - a node and whose steps are as follows: - - - - For each child node child of node: - - - Resolve text length of child. - - Child nodes are adjusted before parent nodes. - - - - - - If node is a ‘text’ or ‘tspan’ node - and if the node has a valid ‘textLength’ attribute value: - - - Let a = +∞ and b = −∞. - - - Let i and j be the global - index of the first character and last characters - in node, respectively. - - - For each index k in the range - [i, j] where the "addressable" flag - of result[k] is true: - - This loop finds the left-(top-) most and - right-(bottom-) most extents of the typographic characters within the node and checks for - forced line breaks. - - - - If the character at k is a linefeed - or carriage return, return. No adjustments due to - ‘textLength’ are made to a node with - a forced line break. - - - Let pos = the x coordinate of the position - in result[k], if the "horizontal" - flag is true, and the y coordinate otherwise. - - - Let advance = the advance of - the typographic character corresponding to - character k. [NOTE: This advance will be - negative for RTL horizontal text.] - - - Set a = - min(a, pos, pos - + advance). - - - Set b = - max(b, pos, pos - + advance). - - - - - If a ≠ +∞ then: - - - Find the distance delta = ‘textLength’ - computed value − (b − a). - - User agents are required to shift the last - typographic character in the node by - delta, in the positive x direction - if the "horizontal" flag is true and if - direction is - lrt, in the - negative x direction if the "horizontal" flag - is true and direction is - rtl, or in the - positive y direction otherwise. User agents - are free to adjust intermediate - typographic characters for optimal - typography. The next steps indicate one way to - adjust typographic characters when - the value of ‘lengthAdjust’ is - spacing. - - - - Find n, the total number of - typographic characters in this node - including any descendant nodes that are not resolved - descendant nodes or within a resolved descendant - node. - - - Let n = n + number of - resolved descendant nodes − 1. - - Each resolved descendant node is treated as if it - were a single - typographic character in this - context. - - - - Find the per-character adjustment δ - = delta/n. - - - Let shift = 0. - - - For each index k in the range [i,j]: - - - Add shift to the x coordinate of the - position in result[k], if the "horizontal" - flag is true, and to the y coordinate - otherwise. - - - If the "middle" flag for result[k] - is not true and k is not a character in - a resolved descendant node other than the first - character then shift = shift - + δ. - - - - + The ‘dx’ and ‘dy’ adjustments are applied + before adjustments due to the ‘textLength’ attribute while + the ‘x’, ‘y’ and ‘rotate’ + adjustments are applied after. + Let shift be the cumulative x and + y shifts due to ‘x’ and ‘y’ + attributes, initialized to (0,0). +*/ + PointF shift = new PointF(0, 0); +/* + For each array element with index i in result: +*/ + for (int i = 0; i < count; i++) { +/* + If resolve_x[i] is unspecified, set it to 0. + If resolve_y[i] is unspecified, set it to 0. +*/ + if (resolve_x[i].equals("")) { + resolve_x[i] = "0"; + } + if (resolve_y[i].equals("")) { + resolve_y[i] = "0"; + } +/* + Let shift.x = shift.x + resolve_x[i] + and shift.y = shift.y + resolve_y[i]. +*/ + shift.x = shift.x + Float.parseFloat(resolve_x[i]); + shift.y = shift.y + Float.parseFloat(resolve_y[i]); +/* + Let result[i].x = CSS_positions[i].x + shift.x + and result[i].y = CSS_positions[i].y + shift.y. +*/ + result[i].x = CSS_positions[i].x + shift.x; + result[i].y = CSS_positions[i].y + shift.y; + } +/* + TODO Apply ‘textLength’ attribute + + Set up: + + Define resolved descendant node as a + descendant of node with a valid ‘textLength’ + attribute that is not itself a descendant node of a + descendant node that has a valid ‘textLength’ + attribute. + + Call the following procedure with the ‘text’ element + node. + + Procedure: resolve text length: + + A recursive procedure that takes as input + a node and whose steps are as follows: + For each child node child of node: + + Resolve text length of child. + + Child nodes are adjusted before parent nodes. +*/ + class TextLengthResolver { + int global; + + private void resolveTextLength(TextShadowNode node) { + /* + + If node is a ‘text’ or ‘tspan’ node + and if the node has a valid ‘textLength’ attribute value: +*/ + final Class nodeClass = node.getClass(); + final boolean validTextLength = node.mTextLength != null; + if ( + (nodeClass == TSpanShadowNode.class) + && validTextLength + ) { + /* + Let a = +∞ and b = −∞. +*/ + double a = Double.POSITIVE_INFINITY; + double b = Double.NEGATIVE_INFINITY; +/* + Let i and j be the global + index of the first character and last characters + in node, respectively. +*/ + String content = ((TSpanShadowNode) node).mContent; + int i = global; + int j = i + (content == null ? 0 : content.length()); +/* + For each index k in the range + [i, j] where the "addressable" flag + of result[k] is true: + + This loop finds the left-(top-) most and + right-(bottom-) most extents of the typographic characters within the node and checks for + forced line breaks. +*/ + for (int k = i; k <= j; k++) { + if (!result[i].addressable) { + continue; + } +/* + If the character at k is a linefeed + or carriage return, return. No adjustments due to + ‘textLength’ are made to a node with + a forced line break. +*/ + switch (result[i].character) { + case '\n': + case '\r': + return; + } +/* + Let pos = the x coordinate of the position + in result[k], if the "horizontal" + flag is true, and the y coordinate otherwise. +*/ + double pos = horizontal ? result[k].x : result[k].y; +/* + Let advance = the advance of + the typographic character corresponding to + character k. [NOTE: This advance will be + negative for RTL horizontal text.] +*/ + double advance = result[k].advance; +/* + Set a = + min(a, pos, pos + + advance). + + + Set b = + max(b, pos, pos + + advance). +*/ + a = Math.min(a, Math.min(pos, pos + advance)); + b = Math.max(b, Math.max(pos, pos + advance)); + } +/* + If a ≠ +∞ then: +*/ + if (a != Double.POSITIVE_INFINITY) { +/* + Find the distance delta = ‘textLength’ + computed value − (b − a). +*/ + double delta = Double.parseDouble(node.mTextLength) - (b - a); +/* + User agents are required to shift the last + typographic character in the node by + delta, in the positive x direction + if the "horizontal" flag is true and if + direction is + lrt, in the + negative x direction if the "horizontal" flag + is true and direction is + rtl, or in the + positive y direction otherwise. User agents + are free to adjust intermediate + typographic characters for optimal + typography. The next steps indicate one way to + adjust typographic characters when + the value of ‘lengthAdjust’ is + spacing. + + Find n, the total number of + typographic characters in this node + TODO including any descendant nodes that are not resolved + descendant nodes or within a resolved descendant + node. +*/ + int n = 0; + int resolvedDescendantNodes = 0; + for (int c = 0; c < node.getChildCount(); c++) { + if (((TextPathShadowNode) node.getChildAt(c)).mTextLength == null) { + String ccontent = ((TSpanShadowNode) node).mContent; + n += ccontent == null ? 0 : ccontent.length(); + } else { + result[n].firstCharacterInResolvedDescendant = true; + resolvedDescendantNodes++; + } + } +/* + Let n = n + number of + resolved descendant nodes − 1. +*/ + n += resolvedDescendantNodes - 1; +/* + Each resolved descendant node is treated as if it + were a single + typographic character in this + context. + + Find the per-character adjustment δ + = delta/n. + + Let shift = 0. +*/ + double perCharacterAdjustment = delta / n; + double shift = 0; +/* + For each index k in the range [i,j]: +*/ + for (int k = i; k <= j; k++) { +/* + Add shift to the x coordinate of the + position in result[k], if the "horizontal" + flag is true, and to the y coordinate + otherwise. +*/ + if (horizontal) { + result[k].x += shift; + } else { + result[k].y += shift; + } +/* + If the "middle" flag for result[k] + is not true and k is not a character in + a resolved descendant node other than the first + character then shift = shift + + δ. + */ + if (!result[k].middle && (!result[k].resolved || result[k].firstCharacterInResolvedDescendant)) { + shift += perCharacterAdjustment; + } + } + } + } + } + } + TextLengthResolver lengthResolver = new TextLengthResolver(); + lengthResolver.resolveTextLength(text); +/* Adjust positions: x, y - This loop applies ‘x’ and ‘y’ values, - and ensures that text-anchor chunks do not start in - the middle of a typographic character. - - - - Let shift be the current adjustment due to - the ‘x’ and ‘y’ attributes, - initialized to (0,0). - - - Set index = 1. - - - While index < count: - - - If resolved_x[index] is set, then let - shift.x = - resolved_x[index] − - result.x[index]. - - - If resolved_y[index] is set, then let - shift.y = - resolved_y[index] − - result.y[index]. - - - Let result.x[index] = - result.x[index] + shift.x - and result.y[index] = - result.y[index] + shift.y. - - - If the "middle" and "anchored chunk" flags - of result[index] are both true, then: - - - Set the "anchored chunk" flag - of result[index] to false. - - - If index + 1 < count, then set - the "anchored chunk" flag - of result[index + 1] to true. - - - - - Set index to index + 1. - - + This loop applies ‘x’ and ‘y’ values, + and ensures that text-anchor chunks do not start in + the middle of a typographic character. + Let shift be the current adjustment due to + the ‘x’ and ‘y’ attributes, + initialized to (0,0). + Set index = 1. +*/ + shift.set(0, 0); + int index = 1; +/* + While index < count: +*/ + while (index < count) { +/* + TODO If resolved_x[index] is set, then let (typo) + shift.x = + resolved_x[index] − + result.x[index]. +*/ + if (resolve_x[index] != null) { + shift.x = (float) (Double.parseDouble(resolve_x[index]) - result[index].x); + } +/* + TODO If resolved_y[index] is set, then let (typo) + shift.y = + resolved_y[index] − + result.y[index]. +*/ + if (resolve_y[index] != null) { + shift.y = (float) (Double.parseDouble(resolve_y[index]) - result[index].y); + } +/* + Let result.x[index] = + result.x[index] + shift.x + and result.y[index] = + result.y[index] + shift.y. +*/ + result[index].x += shift.x; + result[index].y += shift.y; +/* + If the "middle" and "anchored chunk" flags + of result[index] are both true, then: +*/ + if (result[index].middle && result[index].anchoredChunk) { +/* + Set the "anchored chunk" flag + of result[index] to false. +*/ + result[index].anchoredChunk = false; + } +/* + If index + 1 < count, then set + the "anchored chunk" flag + of result[index + 1] to true. +*/ + if (index + 1 < count) { + result[index + 1].anchoredChunk = true; + } +/* + Set index to index + 1. +*/ + index++; + } +/* Apply anchoring - - For each slice result[i..j] - (inclusive of both i and j), where: - - - the "anchored chunk" flag of result[i] - is true, - - - the "anchored chunk" flags - of result[k] where i - < k ≤ j are false, and - - - j = count − 1 or the "anchored - chunk" flag of result[j + 1] is - true; - - - do: - - This loops over each anchored chunk. - - - - Let a = +∞ and b = −∞. - - - For each index k in the range - [i, j] where the "addressable" flag - of result[k] is true: - - This loop finds the left-(top-) most and - right-(bottom-) most extents of the typographic character within the anchored chunk. - - - - Let pos = the x coordinate of the position - in result[k], if the "horizontal" flag - is true, and the y coordinate otherwise. - - - Let advance = the advance of - the typographic character corresponding to - character k. [NOTE: This advance will be - negative for RTL horizontal text.] - - - Set a = - min(a, pos, pos - + advance). - - - Set b = - max(b, pos, pos - + advance). - - - - - If a ≠ +∞, then: - - Here we perform the text anchoring. - - - - Let shift be the x coordinate of - result[i], if the "horizontal" flag - is true, and the y coordinate otherwise. - - - Adjust shift based on the value of text-anchor - and direction of the element the character at - index i is in: - - (start, ltr) or (end, rtl) - Set shift = shift − a. - (start, rtl) or (end, ltr) - Set shift = shift − b. - (middle, ltr) or (middle, rtl) - Set shift = shift − (a + b) / 2. - - - - For each index k in the range [i, j]: - - - Add shift to the x coordinate of the position - in result[k], if the "horizontal" - flag is true, and to the y coordinate otherwise. - - - - - - - - - + TODO For each slice result[i..j] + (inclusive of both i and j), where: + + the "anchored chunk" flag of result[i] + is true, + + the "anchored chunk" flags + of result[k] where i + < k ≤ j are false, and + + j = count − 1 or the "anchored + chunk" flag of result[j + 1] is + true; + do: + + This loops over each anchored chunk. + + Let a = +∞ and b = −∞. + + For each index k in the range + [i, j] where the "addressable" flag + of result[k] is true: + + This loop finds the left-(top-) most and + right-(bottom-) most extents of the typographic character within the anchored chunk. +*/ + int i = 0; + double a = Double.POSITIVE_INFINITY; + double b = Double.NEGATIVE_INFINITY; + double prevA = Double.POSITIVE_INFINITY; + double prevB = Double.NEGATIVE_INFINITY; + for (int k = 0; k < count; k++) { + if (!result[k].addressable) { + continue; + } + if (result[k].anchoredChunk) { + prevA = a; + prevB = b; + a = Double.POSITIVE_INFINITY; + b = Double.NEGATIVE_INFINITY; + } +/* + Let pos = the x coordinate of the position + in result[k], if the "horizontal" flag + is true, and the y coordinate otherwise. + + Let advance = the advance of + the typographic character corresponding to + character k. [NOTE: This advance will be + negative for RTL horizontal text.] + + Set a = + min(a, pos, pos + + advance). + + Set b = + max(b, pos, pos + + advance). +*/ + double pos = horizontal ? result[k].x : result[k].y; + double advance = result[k].advance; + a = Math.min(a, Math.min(pos, pos + advance)); + b = Math.max(b, Math.max(pos, pos + advance)); +/* + If a ≠ +∞, then: + + Here we perform the text anchoring. + + Let shift be the x coordinate of + result[i], if the "horizontal" flag + is true, and the y coordinate otherwise. + + TODO Adjust shift based on the value of text-anchor + TODO and direction of the element the character at + index i is in: + + (start, ltr) or (end, rtl) + Set shift = shift − a. + (start, rtl) or (end, ltr) + Set shift = shift − b. + (middle, ltr) or (middle, rtl) + Set shift = shift − (a + b) / 2. +*/ + if ((k > 0 && result[k].anchoredChunk && prevA != Double.POSITIVE_INFINITY) || k == count - 1) { + TextAnchor anchor = TextAnchor.start; + Direction direction = Direction.ltr; + + if (k == count - 1) { + prevA = a; + prevB = b; + } + + double anchorShift = horizontal ? result[i].x : result[i].y; + switch (anchor) { + case start: + if (direction == Direction.ltr) { + anchorShift = anchorShift - prevA; + } else { + anchorShift = anchorShift - prevB; + } + break; + + case middle: + if (direction == Direction.ltr) { + anchorShift = anchorShift - (prevA + prevB) / 2; + } else { + anchorShift = anchorShift - (prevA + prevB) / 2; + } + break; + + case end: + if (direction == Direction.ltr) { + anchorShift = anchorShift - prevB; + } else { + anchorShift = anchorShift - prevA; + } + break; + } +/* + For each index k in the range [i, j]: + + Add shift to the x coordinate of the position + in result[k], if the "horizontal" + flag is true, and to the y coordinate otherwise. +*/ + int j = k == count - 1 ? k : k - 1; + for (int r = i; r <= j; r++) { + if (horizontal) { + result[r].x += anchorShift; + } else { + result[r].y += anchorShift; + } + } + + i = k; + } + } +/* Position on path + Set index = 0. - Set index = 0. - - - Set the "in path" flag to false. - - - Set the "after path" flag to false. - - - Let path_end be an offset for characters that follow - a ‘textPath’ element. Set path_end to (0,0). - - - While index < count: - - - If the character at index i is within a - ‘textPath’ element and corresponds to a typographic character, then: - - - Set "in path" flag to true. - - - If the "middle" flag of - result[index] is false, then: - - Here we apply ‘textPath’ positioning. + Set the "in path" flag to false. + Set the "after path" flag to false. + Let path_end be an offset for characters that follow + a ‘textPath’ element. Set path_end to (0,0). - Let path be the equivalent path of - the basic shape element referenced by - the ‘textPath’ element, or an empty path if - the reference is invalid. - - - If the ‘side’ attribute of - the ‘textPath’ element is - 'right', then - reverse path. - - - Let length be the length - of path. - - - Let offset be the value of the - ‘textPath’ element's - ‘startOffset’ attribute, adjusted - due to any ‘pathLength’ attribute on the - referenced element (if the referenced element is - a ‘path’ element). - - - Let advance = the advance of - the typographic character corresponding - to character k. [NOTE: This advance will - be negative for RTL horizontal text.] - - - Let (x, y) - and angle be the position and angle - in result[index]. - - - Let mid be a coordinate value depending - on the value of the "horizontal" flag: - - true - mid is x + advance / 2 - + offset - false - mid is y + advance / 2 - + offset - - - The user agent is free to make any additional adjustments to - mid necessary to ensure high quality typesetting - due to a ‘spacing’ value of - 'auto' or a - ‘method’ value of - 'stretch'. - - - - If path is not a closed subpath and - mid < 0 or mid > length, - set the "hidden" flag of result[index] to true. - - - If path is a closed subpath depending on - the values of text-anchor and direction of - the element the character at index is in: - - This implements the special wrapping criteria for single - closed subpaths. - - - (start, ltr) or (end, rtl) - - If mid−offset < 0 - or mid−offset > length, - set the "hidden" flag of result[index] to true. - - (middle, ltr) or (middle, rtl) - - If - If mid−offset < −length/2 - or mid−offset > length/2, - set the "hidden" flag of result[index] to true. - - (start, rtl) or (end, ltr) - - If mid−offset < −length - or mid−offset > 0, - set the "hidden" flag of result[index] to true. - - - - Set mid = mid mod length. - - - If the hidden flag is false: - - - Let point be the position and - t be the unit vector tangent to - the point mid distance - along path. - - - If the "horizontal" flag is - - true - - - - Let n be the normal unit vector - pointing in the direction t + 90°. - - - Let o be the horizontal distance from the - vertical center line of the glyph to the alignment point. - - - Then set the position in - result[index] to - point - - o×t + - y×n. - - - Let r be the angle from - the positive x-axis to the tangent. - - - Set the angle value - in result[index] - to angle + r. - - - - false - - - - Let n be the normal unit vector - pointing in the direction t - 90°. - - - Let o be the vertical distance from the - horizontal center line of the glyph to the alignment point. - - - Then set the position in - result[index] to - point - - o×t + - x×n. - - - Let r be the angle from - the positive y-axis to the tangent. - - - Set the angle value - in result[index] - to angle + r. - - - - - - - - - - - Otherwise, the "middle" flag - of result[index] is true: - - - Set the position and angle values - of result[index] to those - in result[index − 1]. - - - - - - - If the character at index i is not within a - ‘textPath’ element and corresponds to a typographic character, then: - - This sets the starting point for rendering any characters that - occur after a ‘textPath’ element to the end of the path. - - - If the "in path" flag is true: - - - Set the "in path" flag to false. - - - Set the "after path" flag to true. - - - Set path_end equal to the end point of the path - referenced by ‘textPath’ − the position of - result[index]. - - - - - If the "after path" is true. - - - If anchored chunk of - result[index] is true, set the - "after path" flag to false. - - - Else, - let result.x[index] = - result.x[index] + path_end.x - and result.y[index] = - result.y[index] + path_end.y. + While index < count: +*/ + index = 0; + boolean inPath = false; + boolean afterPath = false; + PointF path_end = new PointF(0, 0); + Path textPath = null; + PathMeasure pm = new PathMeasure(); + while (index < count) { +/* + If the character at index i is within a + ‘textPath’ element and corresponds to a typographic character, then: + + Set "in path" flag to true. +*/ + final TextPathShadowNode textPathShadowNode = inTextPath.get(index); + if (textPathShadowNode != null && result[index].addressable) { + textPath = textPathShadowNode.getPath(); + inPath = true; +/* + If the "middle" flag of + result[index] is false, then: +*/ + if (!result[index].middle) { +/* + Here we apply ‘textPath’ positioning. + + Let path be the equivalent path of + the basic shape element referenced by + the ‘textPath’ element, or an empty path if + the reference is invalid. + + If the ‘side’ attribute of + the ‘textPath’ element is + 'right', then + TODO reverse path. +*/ + Path path = textPath; + if (textPathShadowNode.getSide() == TextPathSide.right) { + + } +/* + Let length be the length + of path. +*/ + pm.setPath(path, false); + double length = pm.getLength(); +/* + Let offset be the value of the + ‘textPath’ element's + ‘startOffset’ attribute, adjusted + due to any ‘pathLength’ attribute on the + referenced element (if the referenced element is + a ‘path’ element). +*/ + double offset = Double.parseDouble(textPathShadowNode.getStartOffset()); +/* + Let advance = the advance of + the typographic character corresponding + to character TODO k. (typo) [NOTE: This advance will + be negative for RTL horizontal text.] +*/ + double advance = result[index].advance; +/* + Let (x, y) + and angle be the position and angle + in result[index]. +*/ + double x = result[index].x; + double y = result[index].y; + double angle = result[index].rotate; +/* + Let mid be a coordinate value depending + on the value of the "horizontal" flag: + + true + mid is x + advance / 2 + + offset + false + mid is y + advance / 2 + + offset +*/ + double mid = (horizontal ? x : y) + advance / 2 + offset; +/* + The user agent is free to make any additional adjustments to + mid necessary to ensure high quality typesetting + TODO due to a ‘spacing’ value of + 'auto' or a + ‘method’ value of + 'stretch'. + + If path is not a closed subpath and + mid < 0 or mid > length, + set the "hidden" flag of result[index] to true. +*/ + if (!pm.isClosed() && (mid < 0 || mid > length)) { + result[index].hidden = true; + } +/* + If path is a closed subpath depending on + the values of text-anchor and direction of + the element the character at index is in: +*/ + if (pm.isClosed()) { +/* + This implements the special wrapping criteria for single + closed subpaths. + + (start, ltr) or (end, rtl) + + If mid−offset < 0 + or mid−offset > length, + set the "hidden" flag of result[index] to true. + + (middle, ltr) or (middle, rtl) + + If + If mid−offset < −length/2 + or mid−offset > length/2, + set the "hidden" flag of result[index] to true. + + (start, rtl) or (end, ltr) + + If mid−offset < −length + or mid−offset > 0, + set the "hidden" flag of result[index] to true. +*/ + TextAnchor anchor = TextAnchor.start; + Direction direction = Direction.ltr; + + double anchorShift = horizontal ? result[i].x : result[i].y; + switch (anchor) { + case start: + if (direction == Direction.ltr) { + if (mid < 0 || mid > length) { + result[index].hidden = true; + } + } else { + if (mid < -length || mid > 0) { + result[index].hidden = true; + } + } + break; + + case middle: + if (mid < -length / 2 || mid > length / 2) { + result[index].hidden = true; + } + break; + + case end: + if (direction == Direction.ltr) { + if (mid < -length || mid > 0) { + result[index].hidden = true; + } + } else { + if (mid < 0 || mid > length) { + result[index].hidden = true; + } + } + break; + } + } +/* + Set mid = mid mod length. +*/ + mid %= length; +/* + If the hidden flag is false: +*/ + if (!result[index].hidden) { +/* + Let point be the position and + t be the unit vector tangent to + the point mid distance + along path. +*/ + float[] point = new float[2]; + float[] t = new float[2]; + pm.getPosTan((float) mid, point, t); + final double tau = 2 * Math.PI; + final double radToDeg = 360 / tau; + final double r = Math.atan2(t[1], t[0]) * radToDeg; +/* + If the "horizontal" flag is +*/ + if (horizontal) { +/* + true + Let n be the normal unit vector + pointing in the direction t + 90°. +*/ + double normAngle = r + 90; + double[] n = new double[]{Math.cos(normAngle), Math.sin(normAngle)}; +/* + Let o be the horizontal distance from the + TODO vertical center line of the glyph to the alignment point. +*/ + double o = 0; +/* + Then set the position in + result[index] to + point - + o×t + + y×n. + + Let r be the angle from + the positive x-axis to the tangent. + + Set the angle value + in result[index] + to angle + r. +*/ + result[index].rotate += r; + } else { +/* + false + Let n be the normal unit vector + pointing in the direction t - 90°. +*/ + double normAngle = r - 90; + double[] n = new double[]{Math.cos(normAngle), Math.sin(normAngle)}; +/* + Let o be the vertical distance from the + TODO horizontal center line of the glyph to the alignment point. +*/ + double o = 0; +/* - Set index = index + 1. + Then set the position in + result[index] to + point - + o×t + + x×n. + + Let r be the angle from + the positive y-axis to the tangent. + + Set the angle value + in result[index] + to angle + r. +*/ + result[index].rotate += r; + } + } +/* + Otherwise, the "middle" flag + of result[index] is true: + Set the position and angle values + of result[index] to those + in result[index − 1]. +*/ + } else { + result[index].x = result[index - 1].x; + result[index].y = result[index - 1].y; + result[index].rotate = result[index - 1].rotate; + } + } +/* + If the character at index i is not within a + ‘textPath’ element and corresponds to a typographic character, then: + This sets the starting point for rendering any characters that + occur after a ‘textPath’ element to the end of the path. +*/ + if (textPathShadowNode == null && result[index].addressable) { +/* + If the "in path" flag is true: + + Set the "in path" flag to false. + + Set the "after path" flag to true. + + Set path_end equal to the end point of the path + referenced by ‘textPath’ − the position of + result[index]. +*/ + if (inPath) { + inPath = false; + afterPath = true; + pm.setPath(textPath, false); + float[] pos = new float[2]; + pm.getPosTan(pm.getLength(), pos, null); + path_end.set(pos[0], pos[1]); + } +/* + If the "after path" is true. + + If anchored chunk of + result[index] is true, set the + "after path" flag to false. + + Else, + let result.x[index] = + result.x[index] + path_end.x + and result.y[index] = + result.y[index] + path_end.y. +*/ + if (afterPath) { + if (result[index].anchoredChunk) { + afterPath = false; + } else { + result[index].x += path_end.x; + result[index].y += path_end.y; + } + } + } +/* + Set index = index + 1. +*/ + index++; + } +/* Return result - - */ +*/ + return result; } } From 2ddb0bddc61677be1b92820129361c7b5d522b88 Mon Sep 17 00:00:00 2001 From: magicismight Date: Tue, 8 Aug 2017 11:25:38 +0800 Subject: [PATCH 167/198] Fix strokeDasharray.concat(strokeDasharray) `strokeDasharray.concat(strokeDasharray)` has created a new array should assign to strokeDasharray. Remove devDependencies --- lib/extract/extractStroke.js | 2 +- package.json | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 53bc40696..56b6426f4 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -39,7 +39,7 @@ export default function(props, styleProperties) { // 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); + strokeDasharray = strokeDasharray.concat(strokeDasharray); } } diff --git a/package.json b/package.json index dc4f5286a..c50cfcb75 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,7 @@ "devDependencies": { "babel-eslint": "^6.1.2", "eslint": "^2.13.1", - "eslint-plugin-react": "^4.3.0", - "prop-types": "^15.5.10", - "react": "16.0.0-alpha.12", - "react-native": ">=0.46.0" + "eslint-plugin-react": "^4.3.0" }, "nativePackage": true } From b62c407b41def8c78891a2e22f4a0edc0f96014d Mon Sep 17 00:00:00 2001 From: magicismight Date: Tue, 8 Aug 2017 13:08:18 +0800 Subject: [PATCH 168/198] Hoist node.saveDefinition Run node.saveDefinition before draw --- .../java/com/horcrux/svg/DefsShadowNode.java | 13 +++-- .../com/horcrux/svg/SvgViewShadowNode.java | 48 ++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java index dbbb272b5..8e4fc42e3 100644 --- a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java @@ -19,11 +19,6 @@ class DefsShadowNode extends DefinitionShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { - traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - node.saveDefinition(); - } - }); NodeRunnable markUpdateSeenRecursive = new NodeRunnable() { public void run(VirtualNode node) { node.markUpdateSeen(); @@ -32,4 +27,12 @@ public void run(VirtualNode node) { }; traverseChildren(markUpdateSeenRecursive); } + + void saveDefinition() { + traverseChildren(new NodeRunnable() { + public void run(VirtualNode node) { + node.saveDefinition(); + } + }); + } } diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index af4f8944c..f4c295d34 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -22,6 +22,7 @@ import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.annotations.ReactProp; @@ -126,7 +127,7 @@ Rect getCanvasBounds() { return mCanvas.getClipBounds(); } - private void drawChildren(Canvas canvas) { + private void drawChildren(final Canvas canvas) { if (mAlign != null) { RectF vbRect = getViewBox(); @@ -135,29 +136,31 @@ private void drawChildren(Canvas canvas) { canvas.concat(mViewBoxMatrix); } - Paint paint = new Paint(); + final Paint paint = new Paint(); paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG); paint.setTypeface(Typeface.DEFAULT); - for (int i = 0; i < getChildCount(); i++) { - if (!(getChildAt(i) instanceof VirtualNode)) { - continue; - } - VirtualNode child = (VirtualNode) getChildAt(i); - child.saveDefinition(); - - int count = child.saveAndSetupCanvas(canvas); - child.draw(canvas, paint, 1f); - child.restoreCanvas(canvas, count); - child.markUpdateSeen(); - - if (child.isResponsible() && !mResponsible) { - mResponsible = true; + traverseChildren(new VirtualNode.NodeRunnable() { + public void run(VirtualNode node) { + node.saveDefinition(); } - } + }); + + traverseChildren(new VirtualNode.NodeRunnable() { + public void run(VirtualNode node) { + int count = node.saveAndSetupCanvas(canvas); + node.draw(canvas, paint, 1f); + node.restoreCanvas(canvas, count); + node.markUpdateSeen(); + + if (node.isResponsible() && !mResponsible) { + mResponsible = true; + } + } + }); } @NonNull @@ -229,4 +232,15 @@ void defineBrush(Brush brush, String brushRef) { Brush getDefinedBrush(String brushRef) { return mDefinedBrushes.get(brushRef); } + + void traverseChildren(VirtualNode.NodeRunnable runner) { + for (int i = 0; i < getChildCount(); i++) { + ReactShadowNode child = getChildAt(i); + if (!(child instanceof VirtualNode)) { + continue; + } + + runner.run((VirtualNode) child); + } + } } From f32800bfbd90cf40765083eb42ced567bb910f5a Mon Sep 17 00:00:00 2001 From: magicismight Date: Tue, 8 Aug 2017 15:27:11 +0800 Subject: [PATCH 169/198] Refactor getParentTextRoot and getTextRoot method --- .../java/com/horcrux/svg/GroupShadowNode.java | 7 +-- .../java/com/horcrux/svg/VirtualNode.java | 55 +++++-------------- 2 files changed, 15 insertions(+), 47 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index d26d485b6..3f0bccea5 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -29,7 +29,6 @@ class GroupShadowNode extends RenderableShadowNode { @Nullable ReadableMap mFont; private GlyphContext mGlyphContext; - private GroupShadowNode textRoot; @ReactProp(name = "font") public void setFont(@Nullable ReadableMap font) { @@ -47,11 +46,9 @@ GlyphContext getGlyphContext() { return mGlyphContext; } + @SuppressWarnings("ConstantConditions") GlyphContext getTextRootGlyphContext() { - if (textRoot == null) { - textRoot = getTextRoot(); - } - return textRoot.getGlyphContext(); + return getTextRoot().getGlyphContext(); } void pushGlyphContext() { diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 50f366c86..d153fd492 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -58,7 +58,6 @@ abstract class VirtualNode extends LayoutShadowNode { private SvgViewShadowNode mSvgShadowNode; private Path mCachedClipPath; - private GroupShadowNode mParentTextRoot; private GroupShadowNode mTextRoot; private float canvasHeight = -1; private float canvasWidth = -1; @@ -73,51 +72,12 @@ public boolean isVirtual() { return true; } - GroupShadowNode getTextRoot() { - GroupShadowNode shadowNode = getTextRoot(GroupShadowNode.class); - if (shadowNode == null) { - return getTextRoot(TextShadowNode.class); - } - return shadowNode; - } - - GroupShadowNode getParentTextRoot() { - GroupShadowNode shadowNode = getParentTextRoot(GroupShadowNode.class); - if (shadowNode == null) { - return getParentTextRoot(TextShadowNode.class); - } - return shadowNode; - } - - @android.support.annotation.Nullable - private GroupShadowNode getParentTextRoot(Class shadowNodeClass) { - ReactShadowNode node = this.getParent(); - if (mParentTextRoot == null) { - while (node != null) { - if (node.getClass() == shadowNodeClass) { - mParentTextRoot = (GroupShadowNode)node; - break; - } - - ReactShadowNode parent = node.getParent(); - - if (!(parent instanceof VirtualNode)) { - node = null; - } else { - node = parent; - } - } - } - - return mParentTextRoot; - } - @android.support.annotation.Nullable - private GroupShadowNode getTextRoot(Class shadowNodeClass) { + GroupShadowNode getTextRoot() { VirtualNode node = this; if (mTextRoot == null) { while (node != null) { - if (node.getClass() == shadowNodeClass) { + if (node instanceof GroupShadowNode && ((GroupShadowNode) node).getGlyphContext() != null) { mTextRoot = (GroupShadowNode)node; break; } @@ -135,6 +95,17 @@ private GroupShadowNode getTextRoot(Class shadowNodeClass) { return mTextRoot; } + @android.support.annotation.Nullable + GroupShadowNode getParentTextRoot() { + ReactShadowNode parent = this.getParent(); + if (!(parent instanceof VirtualNode)) { + return null; + } else { + return ((VirtualNode) parent).getTextRoot(); + } + } + + private double getFontSizeFromContext() { GroupShadowNode root = getTextRoot(); if (root == null) { From f3cd34f2e19165882ffebc6580454e35b90d6841 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 11 Aug 2017 02:27:33 +0300 Subject: [PATCH 170/198] Fix ios/objective-c types and errors/red boxes. --- ios/RNSVG.xcodeproj/project.pbxproj | 5 +- ios/RNSVGRenderable.h | 3 +- ios/RNSVGRenderable.m | 82 +++++++++++++---------- ios/Text/RNSVGGlyphContext.h | 2 +- ios/Text/RNSVGGlyphContext.m | 10 +-- ios/Text/RNSVGText.h | 8 +-- ios/Utils/RNSVGPathParser.m | 2 +- ios/Utils/RNSVGPercentageConverter.m | 4 +- ios/ViewManagers/RNSVGRenderableManager.m | 4 +- ios/ViewManagers/RNSVGTextManager.m | 8 +-- 10 files changed, 72 insertions(+), 56 deletions(-) diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index a3cbfaadb..e8a194f68 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -231,7 +231,7 @@ 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = ""; }; 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSVGGlyphContext.h; path = Text/RNSVGGlyphContext.h; sourceTree = ""; }; 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGlyphContext.m; path = Text/RNSVGGlyphContext.m; sourceTree = ""; }; - A361E7A31EB0C33D00646005 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -274,6 +274,7 @@ isa = PBXGroup; children = ( 0CF68AC11AF0540F00FF9E5C /* libRNSVG.a */, + 94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */, ); name = Products; sourceTree = ""; @@ -452,7 +453,7 @@ ); name = "RNSVG-tvOS"; productName = RNSVG; - productReference = A361E7A31EB0C33D00646005 /* libRNSVG-tvOS.a */; + productReference = 94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */; productType = "com.apple.product-type.library.static"; }; /* End PBXNativeTarget section */ diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 8a17190b5..1d56871ae 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -21,11 +21,12 @@ @property (nonatomic, assign) RNSVGCGFCRule fillRule; @property (nonatomic, strong) RNSVGBrush *stroke; @property (nonatomic, assign) CGFloat strokeOpacity; -@property (nonatomic, assign) CGFloat strokeWidth; +@property (nonatomic, assign) NSString *strokeWidth; @property (nonatomic, assign) CGLineCap strokeLinecap; @property (nonatomic, assign) CGLineJoin strokeLinejoin; @property (nonatomic, assign) CGFloat strokeMiterlimit; @property (nonatomic, assign) RNSVGCGFloatArray strokeDasharray; +@property (nonatomic, assign) NSArray *strokeDasharrayData; @property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, copy) NSArray *propList; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index ccf972ead..4f6f0b4ad 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -21,7 +21,7 @@ - (id)init if (self = [super init]) { _fillOpacity = 1; _strokeOpacity = 1; - _strokeWidth = 1; + _strokeWidth = @"1"; _fillRule = kRNSVGCGFCRuleNonzero; } return self; @@ -72,7 +72,7 @@ - (void)setStrokeOpacity:(CGFloat)strokeOpacity _strokeOpacity = strokeOpacity; } -- (void)setStrokeWidth:(CGFloat)strokeWidth +- (void)setStrokeWidth:(NSString*)strokeWidth { if (strokeWidth == _strokeWidth) { return; @@ -108,16 +108,26 @@ - (void)setStrokeMiterlimit:(CGFloat)strokeMiterlimit _strokeMiterlimit = strokeMiterlimit; } -- (void)setStrokeDasharray:(RNSVGCGFloatArray)strokeDasharray +- (void)setStrokeDasharray:(NSArray *)strokeDasharray { - if (strokeDasharray.array == _strokeDasharray.array) { + if (strokeDasharray == _strokeDasharrayData) { return; } if (_strokeDasharray.array) { free(_strokeDasharray.array); } [self invalidate]; - _strokeDasharray = strokeDasharray; + NSUInteger count = strokeDasharray.count; + _strokeDasharray.count = count; + _strokeDasharray.array = nil; + + if (count) { + _strokeDasharray.array = malloc(sizeof(CGFloat) * count); + for (NSUInteger i = 0; i < count; i++) { + _strokeDasharray.array[i] = [strokeDasharray[i] floatValue]; + } + } + _strokeDasharrayData = strokeDasharray; } - (void)setStrokeDashoffset:(CGFloat)strokeDashoffset @@ -134,7 +144,7 @@ - (void)setPropList:(NSArray *)propList if (propList == _propList) { return; } - + _propList = _attributeList = propList; [self invalidate]; } @@ -153,11 +163,11 @@ - (void)renderTo:(CGContextRef)context CGContextSaveGState(context); CGContextConcatCTM(context, self.matrix); CGContextSetAlpha(context, self.opacity); - + [self beginTransparencyLayer:context]; [self renderLayerTo:context]; [self endTransparencyLayer:context]; - + CGContextRestoreGState(context); } @@ -167,23 +177,23 @@ - (void)renderLayerTo:(CGContextRef)context if (!self.fill && !self.stroke) { return; } - + CGPathRef path = [self getPath:context]; [self setHitArea:path]; - + if (self.opacity == 0) { return; } - + CGPathDrawingMode mode = kCGPathStroke; BOOL fillColor = NO; [self clip:context]; - + BOOL evenodd = self.fillRule == kRNSVGCGFCRuleEvenodd; - + if (self.fill) { fillColor = [self.fill applyFillColor:context opacity:self.fillOpacity]; - + if (fillColor) { mode = evenodd ? kCGPathEOFill : kCGPathFill; } else { @@ -195,31 +205,31 @@ - (void)renderLayerTo:(CGContextRef)context painter:[[self getSvgView] getDefinedPainter:self.fill.brushRef] ]; CGContextRestoreGState(context); - + if (!self.stroke) { return; } } } - + if (self.stroke) { - CGContextSetLineWidth(context, self.strokeWidth); + CGContextSetLineWidth(context, [self.strokeWidth floatValue]); CGContextSetLineCap(context, self.strokeLinecap); CGContextSetLineJoin(context, self.strokeLinejoin); RNSVGCGFloatArray dash = self.strokeDasharray; - + if (dash.count) { CGContextSetLineDash(context, self.strokeDashoffset, dash.array, dash.count); } - + if (!fillColor) { CGContextAddPath(context, path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); } - + BOOL strokeColor = [self.stroke applyStrokeColor:context opacity:self.strokeOpacity]; - + if (strokeColor && fillColor) { mode = evenodd ? kCGPathEOFillStroke : kCGPathFillStroke; } else if (!strokeColor) { @@ -228,12 +238,12 @@ - (void)renderLayerTo:(CGContextRef)context CGContextAddPath(context, path); CGContextDrawPath(context, mode); } - + // draw stroke CGContextAddPath(context, path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); - + [self.stroke paint:context opacity:self.strokeOpacity painter:[[self getSvgView] getDefinedPainter:self.stroke.brushRef] @@ -241,7 +251,7 @@ - (void)renderLayerTo:(CGContextRef)context return; } } - + CGContextAddPath(context, path); CGContextDrawPath(context, mode); } @@ -252,14 +262,14 @@ - (void)setHitArea:(CGPathRef)path if ([self getSvgView].responsible) { // Add path to hitArea CGMutablePathRef hitArea = CGPathCreateMutableCopy(path); - + if (self.stroke && self.strokeWidth) { // Add stroke to hitArea - CGPathRef strokePath = CGPathCreateCopyByStrokingPath(hitArea, nil, self.strokeWidth, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit); + CGPathRef strokePath = CGPathCreateCopyByStrokingPath(hitArea, nil, [self.strokeWidth floatValue], self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit); CGPathAddPath(hitArea, nil, strokePath); CGPathRelease(strokePath); } - + _hitArea = CGPathRetain(CFAutorelease(CGPathCreateCopy(hitArea))); CGPathRelease(hitArea); } @@ -277,22 +287,22 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGA if (!_hitArea) { return nil; } - + if (self.active) { if (!event) { self.active = NO; } return self; } - + CGAffineTransform matrix = CGAffineTransformConcat(self.matrix, transform); CGPathRef hitArea = CGPathCreateCopyByTransformingPath(_hitArea, &matrix); BOOL contains = CGPathContainsPoint(hitArea, nil, point, NO); CGPathRelease(hitArea); - + if (contains) { CGPathRef clipPath = [self getClipPath]; - + if (!clipPath) { return self; } else { @@ -314,14 +324,14 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGA - (void)mergeProperties:(__kindof RNSVGRenderable *)target { NSArray *targetAttributeList = [target getAttributeList]; - + if (targetAttributeList.count == 0) { return; } - + NSMutableArray* attributeList = [self.propList mutableCopy]; _originProperties = [[NSMutableDictionary alloc] init]; - + for (NSString *key in targetAttributeList) { [_originProperties setValue:[self valueForKey:key] forKey:key]; if (![attributeList containsObject:key]) { @@ -329,7 +339,7 @@ - (void)mergeProperties:(__kindof RNSVGRenderable *)target [self setValue:[target valueForKey:key] forKey:key]; } } - + _lastMergedList = targetAttributeList; _attributeList = [attributeList copy]; } @@ -339,7 +349,7 @@ - (void)resetProperties for (NSString *key in _lastMergedList) { [self setValue:[_originProperties valueForKey:key] forKey:key]; } - + _lastMergedList = nil; _attributeList = _propList; } diff --git a/ios/Text/RNSVGGlyphContext.h b/ios/Text/RNSVGGlyphContext.h index d796c8431..8fc03bc43 100644 --- a/ios/Text/RNSVGGlyphContext.h +++ b/ios/Text/RNSVGGlyphContext.h @@ -13,7 +13,7 @@ @interface RNSVGGlyphContext : NSObject - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height; -- (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSString *)positionX positionY:(NSString *)positionY; +- (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY; - (void)popContext; - (CTFontRef)getGlyphFont; - (CGPoint)getNextGlyphPoint:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth; diff --git a/ios/Text/RNSVGGlyphContext.m b/ios/Text/RNSVGGlyphContext.m index 0ced39c8a..06dbe2a78 100644 --- a/ios/Text/RNSVGGlyphContext.m +++ b/ios/Text/RNSVGGlyphContext.m @@ -38,16 +38,16 @@ - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height return self; } -- (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSString *)positionX positionY:(NSString *)positionY +- (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY { CGPoint location = _currentLocation; if (positionX) { - location.x = [RNSVGPercentageConverter stringToFloat:positionX relative:_width offset:0]; + location.x = [RNSVGPercentageConverter stringToFloat:[positionX firstObject] relative:_width offset:0]; } if (positionY) { - location.y = [RNSVGPercentageConverter stringToFloat:positionY relative:_height offset:0]; + location.y = [RNSVGPercentageConverter stringToFloat:[positionY firstObject] relative:_height offset:0]; } [_locationContext addObject:[NSValue valueWithCGPoint:location]]; @@ -127,6 +127,8 @@ - (CTFontRef)getGlyphFont NSNumber *fontSize; NSString *fontWeight; NSString *fontStyle; + NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; + f.numberStyle = NSNumberFormatterDecimalStyle; for (NSDictionary *font in [_fontContext reverseObjectEnumerator]) { if (!fontFamily) { @@ -134,7 +136,7 @@ - (CTFontRef)getGlyphFont } if (fontSize == nil) { - fontSize = font[@"fontSize"]; + fontSize = [f numberFromString:font[@"fontSize"]]; } if (!fontWeight) { diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index f6d843b14..b96215e89 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -14,10 +14,10 @@ @interface RNSVGText : RNSVGGroup @property (nonatomic, assign) RNSVGTextAnchor textAnchor; -@property (nonatomic, strong) NSArray *deltaX; -@property (nonatomic, strong) NSArray *deltaY; -@property (nonatomic, strong) NSString *positionX; -@property (nonatomic, strong) NSString *positionY; +@property (nonatomic, strong) NSArray *deltaX; +@property (nonatomic, strong) NSArray *deltaY; +@property (nonatomic, strong) NSArray *positionX; +@property (nonatomic, strong) NSArray *positionY; @property (nonatomic, strong) NSDictionary *font; - (RNSVGText *)getTextRoot; diff --git a/ios/Utils/RNSVGPathParser.m b/ios/Utils/RNSVGPathParser.m index 5479bc5b2..719d81165 100644 --- a/ios/Utils/RNSVGPathParser.m +++ b/ios/Utils/RNSVGPathParser.m @@ -43,7 +43,7 @@ - (CGPathRef)getPath CGMutablePathRef path = CGPathCreateMutable(); NSArray* results = [_pathRegularExpression matchesInString:_d options:0 range:NSMakeRange(0, [_d length])]; _bezierCurves = [[NSMutableArray alloc] init]; - int count = [results count]; + unsigned long count = [results count]; if (count) { NSUInteger i = 0; diff --git a/ios/Utils/RNSVGPercentageConverter.m b/ios/Utils/RNSVGPercentageConverter.m index fe2cb7781..62c9b3aae 100644 --- a/ios/Utils/RNSVGPercentageConverter.m +++ b/ios/Utils/RNSVGPercentageConverter.m @@ -19,7 +19,9 @@ +(void)initialize + (CGFloat)stringToFloat:(NSString *)string relative:(CGFloat)relative offset:(CGFloat)offset { - if (![self isPercentage:string]) { + if (string == nil) { + return offset; + } else if (![self isPercentage:string]) { return [string floatValue] + offset; } else { return [self percentageToFloat:string relative:relative offset:offset]; diff --git a/ios/ViewManagers/RNSVGRenderableManager.m b/ios/ViewManagers/RNSVGRenderableManager.m index 4291886b8..3cfa7bf06 100644 --- a/ios/ViewManagers/RNSVGRenderableManager.m +++ b/ios/ViewManagers/RNSVGRenderableManager.m @@ -25,10 +25,10 @@ - (RNSVGRenderable *)node RCT_EXPORT_VIEW_PROPERTY(fillRule, RNSVGCGFCRule) RCT_EXPORT_VIEW_PROPERTY(stroke, RNSVGBrush) RCT_EXPORT_VIEW_PROPERTY(strokeOpacity, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(strokeWidth, NSString) RCT_EXPORT_VIEW_PROPERTY(strokeLinecap, CGLineCap) RCT_EXPORT_VIEW_PROPERTY(strokeLinejoin, CGLineJoin) -RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, RNSVGCGFloatArray) +RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, NSArray) RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat) RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat) RCT_EXPORT_VIEW_PROPERTY(propList, NSArray) diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m index edbf7a219..45a8ea675 100644 --- a/ios/ViewManagers/RNSVGTextManager.m +++ b/ios/ViewManagers/RNSVGTextManager.m @@ -21,10 +21,10 @@ - (RNSVGRenderable *)node } RCT_EXPORT_VIEW_PROPERTY(textAnchor, RNSVGTextAnchor) -RCT_EXPORT_VIEW_PROPERTY(deltaX, NSArray) -RCT_EXPORT_VIEW_PROPERTY(deltaY, NSArray) -RCT_EXPORT_VIEW_PROPERTY(positionX, NSString) -RCT_EXPORT_VIEW_PROPERTY(positionY, NSString) +RCT_EXPORT_VIEW_PROPERTY(deltaX, NSArray) +RCT_EXPORT_VIEW_PROPERTY(deltaY, NSArray) +RCT_EXPORT_VIEW_PROPERTY(positionX, NSArray) +RCT_EXPORT_VIEW_PROPERTY(positionY, NSArray) RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary) @end From 68eeb5e6daa832dda5d6c9b2b3f15883419bee48 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 15 Aug 2017 13:31:42 +0300 Subject: [PATCH 171/198] Improve numberProp and numberArrayProp PropTypes usage. Implement "normal" relative unit in java. --- .../main/java/com/horcrux/svg/PropHelper.java | 2 +- lib/props.js | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 3eb1adedc..4ddb0a7c5 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -95,7 +95,7 @@ static double fromRelative(String length, double relative, double offset, double length = length.trim(); int stringLength = length.length(); int percentIndex = stringLength - 1; - if (stringLength == 0) { + if (stringLength == 0 || length.equals("normal")) { return offset; } else if (length.codePointAt(percentIndex) == '%') { return Double.valueOf(length.substring(0, percentIndex)) / 100 * relative + offset; diff --git a/lib/props.js b/lib/props.js index 282fa3495..251424cef 100644 --- a/lib/props.js +++ b/lib/props.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import {PanResponder} from 'react-native'; const numberProp = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); +const numberArrayProp = PropTypes.oneOfType([PropTypes.arrayOf(numberProp), numberProp]); const touchableProps = { disabled: PropTypes.bool, @@ -41,7 +42,7 @@ const strokeProps = { stroke: PropTypes.string, strokeWidth: numberProp, strokeOpacity: numberProp, - strokeDasharray: PropTypes.oneOfType([PropTypes.arrayOf(numberProp), PropTypes.string]), + strokeDasharray: numberArrayProp, strokeDashoffset: numberProp, strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']), strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']), @@ -96,7 +97,7 @@ const fontStretch = PropTypes.oneOf(['normal', 'wider', 'narrower', 'ultra-conde // | | | | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size -const fontSize = PropTypes.string; +const fontSize = numberProp; // [[ | ],]* [ | ] | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family @@ -132,15 +133,15 @@ const textDecoration = PropTypes.oneOf(['none', 'underline', 'overline', 'line-t // normal | | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing -const letterSpacing = PropTypes.string; +const letterSpacing = numberProp; // normal | | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/word-spacing -const wordSpacing = PropTypes.string; +const wordSpacing = numberProp; // auto | | inherit // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning -const kerning = PropTypes.string; +const kerning = numberProp; /* Name: font-variant-ligatures @@ -195,7 +196,7 @@ const lengthAdjust = PropTypes.oneOf(['spacing', 'spacingAndGlyphs']); https://svgwg.org/svg2-draft/text.html#TextElementTextLengthAttribute https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength */ -const textLength = PropTypes.string; +const textLength = numberProp; /* 2.2. Transverse Box Alignment: the vertical-align property @@ -218,7 +219,7 @@ const textLength = PropTypes.string; https://www.w3.org/TR/css-inline-3/#transverse-alignment https://drafts.csswg.org/css-inline/#propdef-vertical-align */ -const verticalAlign = PropTypes.string; +const verticalAlign = numberProp; /* Name: alignment-baseline @@ -258,7 +259,7 @@ const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabeti https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift */ -const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), PropTypes.string]); +const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), numberProp]); /* 6.12. Low-level font feature settings control: the font-feature-settings property @@ -375,8 +376,8 @@ const textSpecificProps = { // https://svgwg.org/svg2-draft/text.html#TSpanAttributes const textProps = { ...textSpecificProps, - dx: PropTypes.string, - dy: PropTypes.string, + dx: numberArrayProp, + dy: numberArrayProp, }; /* @@ -404,7 +405,7 @@ const side = PropTypes.oneOf(['left', 'right']); https://svgwg.org/svg2-draft/text.html#TextPathElementStartOffsetAttribute https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath */ -const startOffset = PropTypes.string; +const startOffset = numberProp; /* Name From 1c1e8a860ba539baa5e997a5c2b7114ab4dfbbb3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 15 Aug 2017 15:44:10 +0300 Subject: [PATCH 172/198] Fix type warning. --- ios/RNSVGRenderable.h | 4 ++-- ios/RNSVGRenderable.m | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 1d56871ae..a66ea0b0f 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -25,8 +25,8 @@ @property (nonatomic, assign) CGLineCap strokeLinecap; @property (nonatomic, assign) CGLineJoin strokeLinejoin; @property (nonatomic, assign) CGFloat strokeMiterlimit; -@property (nonatomic, assign) RNSVGCGFloatArray strokeDasharray; -@property (nonatomic, assign) NSArray *strokeDasharrayData; +@property (nonatomic, assign) RNSVGCGFloatArray strokeDasharrayData; +@property (nonatomic, assign) NSArray *strokeDasharray; @property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, copy) NSArray *propList; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 4f6f0b4ad..0ad89c797 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -110,24 +110,24 @@ - (void)setStrokeMiterlimit:(CGFloat)strokeMiterlimit - (void)setStrokeDasharray:(NSArray *)strokeDasharray { - if (strokeDasharray == _strokeDasharrayData) { + if (strokeDasharray == _strokeDasharray) { return; } - if (_strokeDasharray.array) { - free(_strokeDasharray.array); + if (_strokeDasharrayData.array) { + free(_strokeDasharrayData.array); } [self invalidate]; NSUInteger count = strokeDasharray.count; - _strokeDasharray.count = count; - _strokeDasharray.array = nil; + _strokeDasharrayData.count = count; + _strokeDasharrayData.array = nil; if (count) { - _strokeDasharray.array = malloc(sizeof(CGFloat) * count); + _strokeDasharrayData.array = malloc(sizeof(CGFloat) * count); for (NSUInteger i = 0; i < count; i++) { - _strokeDasharray.array[i] = [strokeDasharray[i] floatValue]; + _strokeDasharrayData.array[i] = [strokeDasharray[i] floatValue]; } } - _strokeDasharrayData = strokeDasharray; + _strokeDasharray = strokeDasharray; } - (void)setStrokeDashoffset:(CGFloat)strokeDashoffset @@ -152,8 +152,8 @@ - (void)setPropList:(NSArray *)propList - (void)dealloc { CGPathRelease(_hitArea); - if (_strokeDasharray.array) { - free(_strokeDasharray.array); + if (_strokeDasharrayData.array) { + free(_strokeDasharrayData.array); } } @@ -216,7 +216,7 @@ - (void)renderLayerTo:(CGContextRef)context CGContextSetLineWidth(context, [self.strokeWidth floatValue]); CGContextSetLineCap(context, self.strokeLinecap); CGContextSetLineJoin(context, self.strokeLinejoin); - RNSVGCGFloatArray dash = self.strokeDasharray; + RNSVGCGFloatArray dash = self.strokeDasharrayData; if (dash.count) { CGContextSetLineDash(context, self.strokeDashoffset, dash.array, dash.count); From f1657d9ddf9d36de7563322ea1b24335f734fb6f Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 15 Aug 2017 19:50:50 +0300 Subject: [PATCH 173/198] Prepare for new GlyphContext. --- ios/Elements/RNSVGGroup.h | 6 ++++ ios/Elements/RNSVGGroup.m | 27 ++++++++++++++++ ios/RNSVGNode.h | 16 +++++++++ ios/RNSVGNode.m | 63 ++++++++++++++++++++++++++++++++++++ ios/Text/RNSVGGlyphContext.h | 3 ++ ios/Text/RNSVGGlyphContext.m | 18 +++++++++++ ios/Text/RNSVGTSpan.m | 9 +++++- ios/Text/RNSVGText.h | 6 ---- ios/Text/RNSVGText.m | 33 ------------------- 9 files changed, 141 insertions(+), 40 deletions(-) diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index ff8fac01b..163d8477f 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -12,10 +12,16 @@ #import "RNSVGCGFCRule.h" #import "RNSVGSvgView.h" #import "RNSVGPath.h" +#import "RNSVGGlyphContext.h" @interface RNSVGGroup : RNSVGPath +@property (nonatomic, strong) NSDictionary *font; + - (void)renderPathTo:(CGContextRef)context; - (void)renderGroupTo:(CGContextRef)context; +- (RNSVGGlyphContext *)getGlyphContext; +- (void)pushGlyphContext; +- (void)popGlyphContext; @end diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index d60f7696d..d881981cb 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -9,15 +9,20 @@ #import "RNSVGGroup.h" @implementation RNSVGGroup +{ + RNSVGGlyphContext *_glyphContext; +} - (void)renderLayerTo:(CGContextRef)context { [self clip:context]; + [self setupGlyphContext:context]; [self renderGroupTo:context]; } - (void)renderGroupTo:(CGContextRef)context { + [self pushGlyphContext]; RNSVGSvgView* svg = [self getSvgView]; [self traverseSubviews:^(RNSVGNode *node) { if (node.responsible && !svg.responsible) { @@ -36,6 +41,28 @@ - (void)renderGroupTo:(CGContextRef)context return YES; }]; + [self popGlyphContext]; +} + +- (void)setupGlyphContext:(CGContextRef)context +{ + _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] + height:[self getContextHeight]]; +} + +- (RNSVGGlyphContext *)getGlyphContext +{ + return _glyphContext; +} + +- (void)pushGlyphContext +{ + [[[self getTextRoot] getGlyphContext] pushContext:self.font]; +} + +- (void)popGlyphContext +{ + [[[self getTextRoot] getGlyphContext] popContext]; } - (void)renderPathTo:(CGContextRef)context diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index ef0eb3361..fdae81692 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -9,6 +9,7 @@ #import #import "RNSVGCGFCRule.h" #import "RNSVGSvgView.h" +@class RNSVGGroup; /** * RNSVG nodes are implemented as base UIViews. They should be implementation for all basic @@ -17,6 +18,14 @@ @interface RNSVGNode : UIView +/* + N[1/Sqrt[2], 36] + The inverse of the square root of 2. + Provide enough digits for the 128-bit IEEE quad (36 significant digits). + */ +extern CGFloat const M_SQRT1_2l; +extern CGFloat const DEFAULT_FONT_SIZE; + @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) CGFloat opacity; @property (nonatomic, assign) RNSVGCGFCRule clipRule; @@ -27,6 +36,9 @@ - (void)invalidate; +- (RNSVGGroup *)getTextRoot; +- (RNSVGGroup *)getParentTextRoot; + - (void)renderTo:(CGContextRef)context; /** @@ -70,6 +82,10 @@ - (CGFloat)relativeOnHeight:(NSString *)length; +- (CGFloat)relativeOnOther:(NSString *)length; + +- (CGFloat)getFontSizeFromContext; + - (CGFloat)getContextWidth; - (CGFloat)getContextHeight; diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 8371edd2d..09ef0e3a1 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -9,14 +9,21 @@ #import "RNSVGNode.h" #import "RNSVGContainer.h" #import "RNSVGClipPath.h" +#import "RNSVGGroup.h" +#import "RNSVGGlyphContext.h" @implementation RNSVGNode { + RNSVGGroup *_textRoot; + RNSVGGlyphContext *glyphContext; BOOL _transparent; CGPathRef _cachedClipPath; RNSVGSvgView *_svgView; } +CGFloat const M_SQRT1_2l = 0.707106781186547524400844362104849039; +CGFloat const DEFAULT_FONT_SIZE = 12; + - (instancetype)init { if (self = [super init]) { @@ -49,6 +56,53 @@ - (void)invalidate [container invalidate]; } +- (RNSVGGroup *)getTextRoot +{ + RNSVGNode* node = self; + if (_textRoot == nil) { + while (node != nil) { + if ([node isKindOfClass:[RNSVGGroup class]] && [((RNSVGGroup*) node) getGlyphContext] != nil) { + _textRoot = (RNSVGGroup*)node; + break; + } + + UIView* parent = [node superview]; + + if (![node isKindOfClass:[RNSVGNode class]]) { + node = nil; + } else { + node = (RNSVGNode*)parent; + } + } + } + + return _textRoot; +} + +- (RNSVGGroup *)getParentTextRoot +{ + RNSVGNode* parent = (RNSVGGroup*)[self superview]; + if (![parent isKindOfClass:[RNSVGGroup class]]) { + return nil; + } else { + return [parent getTextRoot]; + } +} + +- (CGFloat)getFontSizeFromContext +{ + RNSVGGroup* root = [self getTextRoot]; + if (root == nil) { + return DEFAULT_FONT_SIZE; + } + + if (glyphContext == nil) { + glyphContext = [root getGlyphContext]; + } + + return [glyphContext getFontSize]; +} + - (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor { self.backgroundColor = inheritedBackgroundColor; @@ -194,6 +248,15 @@ - (CGFloat)relativeOnHeight:(NSString *)length return [RNSVGPercentageConverter stringToFloat:length relative:[self getContextHeight] offset:0]; } +- (CGFloat)relativeOnOther:(NSString *)length +{ + CGFloat width = [self getContextWidth]; + CGFloat height = [self getContextHeight]; + CGFloat powX = width * width; + CGFloat powY = height * height; + CGFloat r = sqrt(powX + powY) * M_SQRT1_2l; + return [RNSVGPercentageConverter stringToFloat:length relative:r offset:0];} + - (CGFloat)getContextWidth { return CGRectGetWidth([[self getSvgView] getContextBounds]); diff --git a/ios/Text/RNSVGGlyphContext.h b/ios/Text/RNSVGGlyphContext.h index 8fc03bc43..96cbc686f 100644 --- a/ios/Text/RNSVGGlyphContext.h +++ b/ios/Text/RNSVGGlyphContext.h @@ -13,6 +13,9 @@ @interface RNSVGGlyphContext : NSObject - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height; + +- (CGFloat)getFontSize; +- (void)pushContext:(NSDictionary *)font; - (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY; - (void)popContext; - (CTFontRef)getGlyphFont; diff --git a/ios/Text/RNSVGGlyphContext.m b/ios/Text/RNSVGGlyphContext.m index 06dbe2a78..7f093f8f0 100644 --- a/ios/Text/RNSVGGlyphContext.m +++ b/ios/Text/RNSVGGlyphContext.m @@ -10,6 +10,7 @@ #import "RNSVGGlyphContext.h" #import "RNSVGPercentageConverter.h" #import +#import "RNSVGNode.h" @implementation RNSVGGlyphContext { @@ -21,6 +22,7 @@ @implementation RNSVGGlyphContext CGPoint _currentLocation; CGFloat _width; CGFloat _height; + CGFloat _fontSize; } - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height @@ -34,10 +36,26 @@ - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height _deltaYContext = [[NSMutableArray alloc] init]; _xContext = [[NSMutableArray alloc] init]; _currentLocation = CGPointZero; + _fontSize = DEFAULT_FONT_SIZE; } return self; } +- (CGFloat) getFontSize +{ + return _fontSize; +} + +- (void)pushContext:(NSDictionary *)font +{ + CGPoint location = _currentLocation; + + [_locationContext addObject:[NSValue valueWithCGPoint:location]]; + [_fontContext addObject:font ? font : @{}]; + [_xContext addObject:[NSNumber numberWithFloat:location.x]]; + _currentLocation = location; +} + - (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY { CGPoint location = _currentLocation; diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 71903525c..af973ee33 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -70,10 +70,17 @@ - (CGPathRef)getPath:(CGContextRef)context CTFontRef font = [self getFontFromContext]; // Create a dictionary for this font - CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{ + CFDictionaryRef attributes; + if (font != nil) { + attributes = (__bridge CFDictionaryRef)@{ (NSString *)kCTFontAttributeName: (__bridge id)font, (NSString *)kCTForegroundColorFromContextAttributeName: @YES }; + } else { + attributes = (__bridge CFDictionaryRef)@{ + (NSString *)kCTForegroundColorFromContextAttributeName: @YES + }; + } CFStringRef string = (__bridge CFStringRef)text; CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index b96215e89..aac064a6f 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -9,7 +9,6 @@ #import #import "RNSVGGroup.h" #import "RNSVGTextAnchor.h" -#import "RNSVGGlyphContext.h" @interface RNSVGText : RNSVGGroup @@ -18,15 +17,10 @@ @property (nonatomic, strong) NSArray *deltaY; @property (nonatomic, strong) NSArray *positionX; @property (nonatomic, strong) NSArray *positionY; -@property (nonatomic, strong) NSDictionary *font; -- (RNSVGText *)getTextRoot; - (void)releaseCachedPath; - (CGPathRef)getGroupPath:(CGContextRef)context; -- (RNSVGGlyphContext *)getGlyphContext; -- (void)pushGlyphContext; -- (void)popGlyphContext; - (CTFontRef)getFontFromContext; - (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth; diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 115cf9ae4..27205f6ad 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -13,10 +13,6 @@ #import "RNSVGGlyphContext.h" @implementation RNSVGText -{ - RNSVGText *_textRoot; - RNSVGGlyphContext *_glyphContext; -} - (void)setTextAnchor:(RNSVGTextAnchor)textAnchor { @@ -28,7 +24,6 @@ - (void)renderLayerTo:(CGContextRef)context { [self clip:context]; CGContextSaveGState(context); - [self setupGlyphContext:context]; CGPathRef path = [self getGroupPath:context]; CGAffineTransform transform = [self getAlignTransform:path]; @@ -43,12 +38,6 @@ - (void)renderLayerTo:(CGContextRef)context CGPathRelease(transformedPath); } -- (void)setupGlyphContext:(CGContextRef)context -{ - _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] - height:[self getContextHeight]]; -} - // release the cached CGPathRef for RNSVGTSpan - (void)releaseCachedPath { @@ -70,7 +59,6 @@ - (CGPathRef)getGroupPath:(CGContextRef)context - (CGPathRef)getPath:(CGContextRef)context { - [self setupGlyphContext:context]; CGPathRef groupPath = [self getGroupPath:context]; CGAffineTransform transform = [self getAlignTransform:groupPath]; [self releaseCachedPath]; @@ -117,27 +105,6 @@ - (RNSVGTextAnchor)getComputedTextAnchor return anchor; } -- (RNSVGText *)getTextRoot -{ - if (!_textRoot) { - _textRoot = self; - while (_textRoot && [_textRoot class] != [RNSVGText class]) { - if (![_textRoot isKindOfClass:[RNSVGText class]]) { - //todo: throw exception here - break; - } - _textRoot = (RNSVGText*)[_textRoot superview]; - } - } - - return _textRoot; -} - -- (RNSVGGlyphContext *)getGlyphContext -{ - return _glyphContext; -} - - (void)pushGlyphContext { [[[self getTextRoot] getGlyphContext] pushContext:self.font From 3cafc34cb2d87adf27596d769974c0d32e8dc8bd Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 16 Aug 2017 19:06:23 +0300 Subject: [PATCH 174/198] Fix ViewBox, images, transforms, and, gradients; on ios. Make getContextWidth/Height and setupGlyphContext take clip bounding box and affine transform matrix into consideration. --- ios/Brushes/RNSVGPainter.m | 28 ++++++++++-------- ios/Elements/RNSVGGroup.m | 31 ++++++++++++-------- ios/Elements/RNSVGImage.m | 37 +++++++++++++---------- ios/RNSVGNode.m | 40 +++++++++++++++---------- ios/Text/RNSVGGlyphContext.h | 2 ++ ios/Text/RNSVGGlyphContext.m | 43 ++++++++++++++++----------- ios/Text/RNSVGTSpan.m | 46 ++++++++++++++--------------- ios/Text/RNSVGText.m | 49 ++++++++++++++++++++++++++----- ios/Utils/RNSVGViewBox.m | 57 ++++++++++++++++++------------------ 9 files changed, 200 insertions(+), 133 deletions(-) diff --git a/ios/Brushes/RNSVGPainter.m b/ios/Brushes/RNSVGPainter.m index c49fd82e6..1ba218803 100644 --- a/ios/Brushes/RNSVGPainter.m +++ b/ios/Brushes/RNSVGPainter.m @@ -50,7 +50,7 @@ - (void)setLinearGradientColors:(NSArray *)colors // todo: throw error return; } - + _type = kRNSVGLinearGradient; _colors = colors; } @@ -61,7 +61,7 @@ - (void)setRadialGradientColors:(NSArray *)colors // todo: throw error return; } - + _type = kRNSVGRadialGradient; _colors = colors; } @@ -84,27 +84,27 @@ - (CGRect)getPaintRect:(CGContextRef)context float width = CGRectGetWidth(rect); float x = 0.0; float y = 0.0; - + if (_useObjectBoundingBox) { x = CGRectGetMinX(rect); y = CGRectGetMinY(rect); } - + return CGRectMake(x, y, width, height); } - (void)paintLinearGradient:(CGContextRef)context { - + CGGradientRef gradient = CGGradientRetain([RCTConvert RNSVGCGGradient:_colors offset:0]); CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; - + CGRect rect = [self getPaintRect:context]; float height = CGRectGetHeight(rect); float width = CGRectGetWidth(rect); float offsetX = CGRectGetMinX(rect); float offsetY = CGRectGetMinY(rect); - + CGFloat x1 = [RNSVGPercentageConverter stringToFloat:(NSString *)[_points objectAtIndex:0] relative:width offset:offsetX]; @@ -117,8 +117,9 @@ - (void)paintLinearGradient:(CGContextRef)context CGFloat y2 = [RNSVGPercentageConverter stringToFloat:(NSString *)[_points objectAtIndex:3] relative:height offset:offsetY]; - - + + + CGContextConcatCTM(context, _transform); CGContextDrawLinearGradient(context, gradient, CGPointMake(x1, y1), CGPointMake(x2, y2), extendOptions); CGGradientRelease(gradient); } @@ -127,13 +128,13 @@ - (void)paintRidialGradient:(CGContextRef)context { CGGradientRef gradient = CGGradientRetain([RCTConvert RNSVGCGGradient:_colors offset:0]); CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; - + CGRect rect = [self getPaintRect:context]; float height = CGRectGetHeight(rect); float width = CGRectGetWidth(rect); float offsetX = CGRectGetMinX(rect); float offsetY = CGRectGetMinY(rect); - + CGFloat rx = [RNSVGPercentageConverter stringToFloat:(NSString *)[_points objectAtIndex:2] relative:width offset:0]; @@ -152,10 +153,11 @@ - (void)paintRidialGradient:(CGContextRef)context CGFloat cy = [RNSVGPercentageConverter stringToFloat:(NSString *)[_points objectAtIndex:5] relative:height offset:offsetY] / (ry / rx); - + CGAffineTransform transform = CGAffineTransformMakeScale(1, ry / rx); CGContextConcatCTM(context, transform); - + + CGContextConcatCTM(context, _transform); CGContextDrawRadialGradient(context, gradient, CGPointMake(fx, fy), 0, CGPointMake(cx, cy), rx, extendOptions); CGGradientRelease(gradient); } diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index d881981cb..c093e90c0 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -28,17 +28,17 @@ - (void)renderGroupTo:(CGContextRef)context if (node.responsible && !svg.responsible) { svg.responsible = YES; } - + if ([node isKindOfClass:[RNSVGRenderable class]]) { [(RNSVGRenderable*)node mergeProperties:self]; } - + [node renderTo:context]; - + if ([node isKindOfClass:[RNSVGRenderable class]]) { [(RNSVGRenderable*)node resetProperties]; } - + return YES; }]; [self popGlyphContext]; @@ -46,8 +46,13 @@ - (void)renderGroupTo:(CGContextRef)context - (void)setupGlyphContext:(CGContextRef)context { - _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] - height:[self getContextHeight]]; + CGRect clipBounds = CGContextGetClipBoundingBox(context); + clipBounds = CGRectApplyAffineTransform(clipBounds, self.matrix); + CGFloat width = CGRectGetWidth(clipBounds); + CGFloat height = CGRectGetHeight(clipBounds); + + _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:width + height:height]; } - (RNSVGGlyphContext *)getGlyphContext @@ -88,7 +93,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGA if (hitSelf) { return hitSelf; } - + CGAffineTransform matrix = CGAffineTransformConcat(self.matrix, transform); CGPathRef clip = [self getClipPath]; @@ -96,26 +101,26 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGA CGPathRef transformedClipPath = CGPathCreateCopyByTransformingPath(clip, &matrix); BOOL insideClipPath = CGPathContainsPoint(clip, nil, point, self.clipRule == kRNSVGCGFCRuleEvenodd); CGPathRelease(transformedClipPath); - + if (!insideClipPath) { return nil; } - + } - + for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) { if (![node isKindOfClass:[RNSVGNode class]]) { continue; } - + if (event) { node.active = NO; } else if (node.active) { return node; } - + UIView *hitChild = [node hitTest: point withEvent:event withTransform:matrix]; - + if (hitChild) { node.active = YES; return (node.responsible || (node != hitChild)) ? hitChild : self; diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m index 97ac9f624..9f3764a87 100644 --- a/ios/Elements/RNSVGImage.m +++ b/ios/Elements/RNSVGImage.m @@ -31,7 +31,7 @@ - (void)setSrc:(id)src } else { _imageRatio = 0.0; } - + _image = CGImageRetain([RCTConvert CGImage:src]); [self invalidate]; } @@ -102,11 +102,11 @@ - (void)renderLayerTo:(CGContextRef)context CGPathRef hitArea = CGPathCreateWithRect(rect, nil); [self setHitArea:hitArea]; CGPathRelease(hitArea); - + CGContextSaveGState(context); CGContextTranslateCTM(context, 0, rect.size.height + 2 * rect.origin.y); CGContextScaleCTM(context, 1, -1); - + // apply viewBox transform on Image render. CGFloat imageRatio = _imageRatio; CGFloat rectWidth = CGRectGetWidth(rect); @@ -115,7 +115,7 @@ - (void)renderLayerTo:(CGContextRef)context CGFloat rectY = CGRectGetMinY(rect); CGFloat rectRatio = rectWidth / rectHeight; CGRect renderRect; - + if (!imageRatio || imageRatio == rectRatio) { renderRect = rect; } else if (imageRatio < rectRatio) { @@ -123,29 +123,34 @@ - (void)renderLayerTo:(CGContextRef)context } else { renderRect = CGRectMake(0, 0, rectWidth, rectWidth / imageRatio); } - + + CGFloat canvasLeft = [self getContextLeft]; + CGFloat canvasTop = [self getContextTop]; CGRect vbRect = CGRectMake(0, 0, CGRectGetWidth(renderRect), CGRectGetHeight(renderRect)); - CGRect eRect = CGRectMake([self getContextLeft], [self getContextTop], rectWidth, rectHeight); - + CGRect eRect = CGRectMake(canvasLeft, canvasTop, rectWidth, rectHeight); + CGAffineTransform transform = [RNSVGViewBox getTransform:vbRect eRect:eRect align:self.align meetOrSlice:self.meetOrSlice fromSymbol:NO]; - + + CGFloat dx = rectX + canvasLeft; + CGFloat dy = rectY + canvasTop; + renderRect = CGRectApplyAffineTransform(renderRect, CGAffineTransformMakeTranslation(-dx, -dy)); renderRect = CGRectApplyAffineTransform(renderRect, transform); - renderRect = CGRectApplyAffineTransform(renderRect, CGAffineTransformMakeTranslation(rectX, rectY)); - + [self clip:context]; CGContextClipToRect(context, rect); - + CGContextDrawImage(context, renderRect, _image); CGContextRestoreGState(context); - + } - (CGRect)getRect:(CGContextRef)context { - return CGRectMake([self relativeOnWidth:self.x], - [self relativeOnHeight:self.y], - [self relativeOnWidth:self.width], - [self relativeOnHeight:self.height]); + CGFloat x = [self relativeOnWidth:self.x]; + CGFloat y = [self relativeOnHeight:self.y]; + CGFloat width = [self relativeOnWidth:self.width]; + CGFloat height = [self relativeOnHeight:self.height]; + return CGRectMake(x, y, x + width, y + height); } - (CGPathRef)getPath:(CGContextRef)context diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 09ef0e3a1..bf2f8dd3d 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -65,9 +65,9 @@ - (RNSVGGroup *)getTextRoot _textRoot = (RNSVGGroup*)node; break; } - + UIView* parent = [node superview]; - + if (![node isKindOfClass:[RNSVGNode class]]) { node = nil; } else { @@ -75,7 +75,7 @@ - (RNSVGGroup *)getTextRoot } } } - + return _textRoot; } @@ -95,11 +95,11 @@ - (CGFloat)getFontSizeFromContext if (root == nil) { return DEFAULT_FONT_SIZE; } - + if (glyphContext == nil) { glyphContext = [root getGlyphContext]; } - + return [glyphContext getFontSize]; } @@ -113,13 +113,13 @@ - (void)setOpacity:(CGFloat)opacity if (opacity == _opacity) { return; } - + if (opacity <= 0) { opacity = 0; } else if (opacity > 1) { opacity = 1; } - + [self invalidate]; _transparent = opacity < 1; _opacity = opacity; @@ -175,14 +175,14 @@ - (CGPathRef)getClipPath:(CGContextRef)context CGPathRelease(_cachedClipPath); _cachedClipPath = CGPathRetain([[[self getSvgView] getDefinedClipPath:self.clipPath] getPath:context]); } - + return [self getClipPath]; } - (void)clip:(CGContextRef)context { CGPathRef clipPath = [self getClipPath:context]; - + if (clipPath) { CGContextAddPath(context, clipPath); if (self.clipRule == kRNSVGCGFCRuleEvenodd) { @@ -207,7 +207,7 @@ - (void)renderLayerTo:(CGContextRef)context // hitTest delagate - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - + // abstract return nil; } @@ -223,9 +223,9 @@ - (RNSVGSvgView *)getSvgView if (_svgView) { return _svgView; } - + __kindof UIView *parent = self.superview; - + if ([parent class] == [RNSVGSvgView class]) { _svgView = parent; } else if ([parent isKindOfClass:[RNSVGNode class]]) { @@ -234,7 +234,7 @@ - (RNSVGSvgView *)getSvgView } else { RCTLogError(@"RNSVG: %@ should be descendant of a SvgViewShadow.", NSStringFromClass(self.class)); } - + return _svgView; } @@ -259,12 +259,22 @@ - (CGFloat)relativeOnOther:(NSString *)length - (CGFloat)getContextWidth { - return CGRectGetWidth([[self getSvgView] getContextBounds]); + RNSVGGroup * root = [self getTextRoot]; + if (root == nil) { + return CGRectGetWidth([[self getSvgView] getContextBounds]); + } else { + return [[root getGlyphContext] getWidth]; + } } - (CGFloat)getContextHeight { - return CGRectGetHeight([[self getSvgView] getContextBounds]); + RNSVGGroup * root = [self getTextRoot]; + if (root == nil) { + return CGRectGetHeight([[self getSvgView] getContextBounds]); + } else { + return [[root getGlyphContext] getHeight]; + } } - (CGFloat)getContextLeft diff --git a/ios/Text/RNSVGGlyphContext.h b/ios/Text/RNSVGGlyphContext.h index 96cbc686f..8fb0cf3b5 100644 --- a/ios/Text/RNSVGGlyphContext.h +++ b/ios/Text/RNSVGGlyphContext.h @@ -14,6 +14,8 @@ - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height; +- (CGFloat)getWidth; +- (CGFloat)getHeight; - (CGFloat)getFontSize; - (void)pushContext:(NSDictionary *)font; - (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY; diff --git a/ios/Text/RNSVGGlyphContext.m b/ios/Text/RNSVGGlyphContext.m index 7f093f8f0..b51d8cffe 100644 --- a/ios/Text/RNSVGGlyphContext.m +++ b/ios/Text/RNSVGGlyphContext.m @@ -41,6 +41,15 @@ - (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height return self; } +- (CGFloat) getWidth +{ + return _width; +} + +- (CGFloat) getHeight { + return _height; +} + - (CGFloat) getFontSize { return _fontSize; @@ -49,7 +58,7 @@ - (CGFloat) getFontSize - (void)pushContext:(NSDictionary *)font { CGPoint location = _currentLocation; - + [_locationContext addObject:[NSValue valueWithCGPoint:location]]; [_fontContext addObject:font ? font : @{}]; [_xContext addObject:[NSNumber numberWithFloat:location.x]]; @@ -59,15 +68,15 @@ - (void)pushContext:(NSDictionary *)font - (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY { CGPoint location = _currentLocation; - + if (positionX) { location.x = [RNSVGPercentageConverter stringToFloat:[positionX firstObject] relative:_width offset:0]; } - + if (positionY) { location.y = [RNSVGPercentageConverter stringToFloat:[positionY firstObject] relative:_height offset:0]; } - + [_locationContext addObject:[NSValue valueWithCGPoint:location]]; [_fontContext addObject:font ? font : @{}]; [_deltaXContext addObject:deltaX ? deltaX : @[]]; @@ -84,11 +93,11 @@ - (void)popContext [_deltaXContext removeLastObject]; [_deltaYContext removeLastObject]; [_xContext removeLastObject]; - + if (_xContext.count) { [_xContext replaceObjectAtIndex:_xContext.count - 1 withObject:x]; } - + if (_locationContext.count) { _currentLocation = [[_locationContext lastObject] CGPointValue]; _currentLocation.x = [x floatValue]; @@ -102,17 +111,17 @@ - (CGPoint)getNextGlyphPoint:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth CGPoint currentLocation = _currentLocation; NSNumber *dx = [self getNextDelta:_deltaXContext]; currentLocation.x += [dx floatValue]; - + NSNumber *dy = [self getNextDelta:_deltaYContext]; currentLocation.y += [dy floatValue]; - + for (NSUInteger i = 0; i < _locationContext.count; i++) { CGPoint point = [[_locationContext objectAtIndex:i] CGPointValue]; point.x += [dx floatValue]; point.y += [dy floatValue]; [_locationContext replaceObjectAtIndex:i withObject:[NSValue valueWithCGPoint:point]]; } - + _currentLocation = currentLocation; NSNumber *x = [NSNumber numberWithFloat:currentLocation.x + offset.x + glyphWidth]; [_xContext replaceObjectAtIndex:_xContext.count - 1 withObject:x]; @@ -128,14 +137,14 @@ - (NSNumber *)getNextDelta:(NSMutableArray *)deltaContext if (value == nil) { value = [delta firstObject]; } - + if (delta.count) { NSMutableArray *mutableDelta = [delta mutableCopy]; [mutableDelta removeObjectAtIndex:0]; [deltaContext replaceObjectAtIndex:index withObject:[mutableDelta copy]]; } } - + return value; } @@ -152,26 +161,26 @@ - (CTFontRef)getGlyphFont if (!fontFamily) { fontFamily = font[@"fontFamily"]; } - + if (fontSize == nil) { fontSize = [f numberFromString:font[@"fontSize"]]; } - + if (!fontWeight) { fontWeight = font[@"fontWeight"]; } if (!fontStyle) { fontStyle = font[@"fontStyle"]; } - + if (fontFamily && fontSize && fontWeight && fontStyle) { break; } } - + BOOL fontFamilyFound = NO; NSArray *supportedFontFamilyNames = [UIFont familyNames]; - + if ([supportedFontFamilyNames containsObject:fontFamily]) { fontFamilyFound = YES; } else { @@ -183,7 +192,7 @@ - (CTFontRef)getGlyphFont } } fontFamily = fontFamilyFound ? fontFamily : nil; - + return (__bridge CTFontRef)[RCTFont updateFont:nil withFamily:fontFamily size:fontSize diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index af973ee33..4aef6bfae 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -53,22 +53,22 @@ - (CGPathRef)getPath:(CGContextRef)context if (_cache) { return _cache; } - + NSString *text = self.content; if (!text) { return [self getGroupPath:context]; } - + [self setupTextPath:context]; - + CGMutablePathRef path = CGPathCreateMutable(); - + // append spacing text = [text stringByAppendingString:@" "]; - + [self pushGlyphContext]; CTFontRef font = [self getFontFromContext]; - + // Create a dictionary for this font CFDictionaryRef attributes; if (font != nil) { @@ -85,19 +85,19 @@ - (CGPathRef)getPath:(CGContextRef)context CFStringRef string = (__bridge CFStringRef)text; CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CTLineRef line = CTLineCreateWithAttributedString(attrString); - + CGMutablePathRef linePath = [self getLinePath:line]; CGAffineTransform offset = CGAffineTransformMakeTranslation(0, _bezierTransformer ? 0 : CTFontGetSize(font) * 1.1); CGPathAddPath(path, &offset, linePath); CGPathRelease(linePath); - + _cache = CGPathRetain(CFAutorelease(CGPathCreateCopy(path))); [self popGlyphContext]; - + // clean up CFRelease(attrString); CFRelease(line); - + return (CGPathRef)CFAutorelease(path); } @@ -107,23 +107,23 @@ - (CGMutablePathRef)getLinePath:(CTLineRef)line CFArrayRef runs = CTLineGetGlyphRuns(line); for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) { CTRunRef run = CFArrayGetValueAtIndex(CTLineGetGlyphRuns(line), i); - + CFIndex runGlyphCount = CTRunGetGlyphCount(run); CGPoint positions[runGlyphCount]; CGGlyph glyphs[runGlyphCount]; - + // Grab the glyphs, positions, and font CTRunGetPositions(run, CFRangeMake(0, 0), positions); CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); - + CGPoint glyphPoint; - + for(CFIndex i = 0; i < runGlyphCount; i++) { CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); - + glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))]; - + CGAffineTransform textPathTransform = CGAffineTransformIdentity; CGAffineTransform transform; if (_bezierTransformer) { @@ -135,19 +135,19 @@ - (CGMutablePathRef)getLinePath:(CTLineRef)line CGPathRelease(letter); continue; } - + textPathTransform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, glyphPoint.y), textPathTransform); transform = CGAffineTransformScale(textPathTransform, 1.0, -1.0); } else { transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(1.0, -1.0), glyphPoint.x, -glyphPoint.y); } - + CGPathAddPath(path, &transform, letter); CGPathRelease(letter); } } - - + + return path; } @@ -161,7 +161,7 @@ - (void)setupTextPath:(CGContextRef)context } return YES; }]; - + _bezierTransformer = bezierTransformer; } @@ -169,13 +169,13 @@ - (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block { RNSVGText *targetView = self; BOOL result = block(self); - + while (targetView && [targetView class] != [RNSVGText class] && result) { if (![targetView isKindOfClass:[RNSVGText class]]) { //todo: throw exception here break; } - + targetView = (RNSVGText*)[targetView superview]; result = block(targetView); } diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 27205f6ad..31fae9fac 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -13,6 +13,10 @@ #import "RNSVGGlyphContext.h" @implementation RNSVGText +{ + RNSVGText *_textRoot; + RNSVGGlyphContext *_glyphContext; +} - (void)setTextAnchor:(RNSVGTextAnchor)textAnchor { @@ -24,20 +28,27 @@ - (void)renderLayerTo:(CGContextRef)context { [self clip:context]; CGContextSaveGState(context); - + [self setupGlyphContext:context]; + CGPathRef path = [self getGroupPath:context]; CGAffineTransform transform = [self getAlignTransform:path]; CGContextConcatCTM(context, transform); [self renderGroupTo:context]; [self releaseCachedPath]; CGContextRestoreGState(context); - - + + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &transform); [self setHitArea:transformedPath]; CGPathRelease(transformedPath); } +- (void)setupGlyphContext:(CGContextRef)context +{ + _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] + height:[self getContextHeight]]; +} + // release the cached CGPathRef for RNSVGTSpan - (void)releaseCachedPath { @@ -53,16 +64,17 @@ - (CGPathRef)getGroupPath:(CGContextRef)context [self pushGlyphContext]; CGPathRef groupPath = [super getPath:context]; [self popGlyphContext]; - + return groupPath; } - (CGPathRef)getPath:(CGContextRef)context { + [self setupGlyphContext:context]; CGPathRef groupPath = [self getGroupPath:context]; CGAffineTransform transform = [self getAlignTransform:groupPath]; [self releaseCachedPath]; - + return (CGPathRef)CFAutorelease(CGPathCreateCopyByTransformingPath(groupPath, &transform)); } @@ -77,7 +89,7 @@ - (CGAffineTransform)getAlignTransform:(CGPathRef)path { CGFloat width = CGRectGetWidth(CGPathGetBoundingBox(path)); CGFloat x = 0; - + switch ([self getComputedTextAnchor]) { case kRNSVGTextAnchorMiddle: x = -width / 2; @@ -87,7 +99,7 @@ - (CGAffineTransform)getAlignTransform:(CGPathRef)path break; default: ; } - + return CGAffineTransformMakeTranslation(x, 0); } @@ -96,7 +108,7 @@ - (RNSVGTextAnchor)getComputedTextAnchor RNSVGTextAnchor anchor = self.textAnchor; if (self.subviews.count > 0) { RNSVGText *child = [self.subviews firstObject]; - + while (child.subviews.count && anchor == kRNSVGTextAnchorAuto) { anchor = child.textAnchor; child = [child.subviews firstObject]; @@ -105,6 +117,27 @@ - (RNSVGTextAnchor)getComputedTextAnchor return anchor; } +- (RNSVGText *)getTextRoot +{ + if (!_textRoot) { + _textRoot = self; + while (_textRoot && [_textRoot class] != [RNSVGText class]) { + if (![_textRoot isKindOfClass:[RNSVGText class]]) { + //todo: throw exception here + break; + } + _textRoot = (RNSVGText*)[_textRoot superview]; + } + } + + return _textRoot; +} + +- (RNSVGGlyphContext *)getGlyphContext +{ + return _glyphContext; +} + - (void)pushGlyphContext { [[[self getTextRoot] getGlyphContext] pushContext:self.font diff --git a/ios/Utils/RNSVGViewBox.m b/ios/Utils/RNSVGViewBox.m index efe1faa3b..29651cb8d 100644 --- a/ios/Utils/RNSVGViewBox.m +++ b/ios/Utils/RNSVGViewBox.m @@ -15,41 +15,40 @@ @implementation RNSVGViewBox + (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice fromSymbol:(BOOL)fromSymbol { // based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform - + // Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively. CGFloat vbX = CGRectGetMinX(vbRect); CGFloat vbY = CGRectGetMinY(vbRect); CGFloat vbWidth = CGRectGetWidth(vbRect); CGFloat vbHeight = CGRectGetHeight(vbRect); - + // Let e-x, e-y, e-width, e-height be the position and size of the element respectively. CGFloat eX = CGRectGetMinX(eRect); CGFloat eY = CGRectGetMinY(eRect); CGFloat eWidth = CGRectGetWidth(eRect); CGFloat eHeight = CGRectGetHeight(eRect); - + // Let align be the align value of preserveAspectRatio, or 'xMidyMid' if preserveAspectRatio is not defined. - + // Let meetOrSlice be the meetOrSlice value of preserveAspectRatio, or 'meet' if preserveAspectRatio is not defined or if meetOrSlice is missing from this value. - + // Initialize scale-x to e-width/vb-width. CGFloat scaleX = eWidth / vbWidth; - + // Initialize scale-y to e-height/vb-height. CGFloat scaleY = eHeight / vbHeight; - - - // Initialize translate-x to vb-x - e-x. - // Initialize translate-y to vb-y - e-y. - CGFloat translateX = vbX - eX; - CGFloat translateY = vbY - eY; - + + // Initialize translate-x to e-x - (vb-x * scale-x). + // Initialize translate-y to e-y - (vb-y * scale-y). + CGFloat translateX = eX - (vbX * scaleX); + CGFloat translateY = eY - (vbY * scaleY); + // If align is 'none' if (meetOrSlice == kRNSVGVBMOSNone) { // Let scale be set the smaller value of scale-x and scale-y. // Assign scale-x and scale-y to scale. CGFloat scale = scaleX = scaleY = fmin(scaleX, scaleY); - + // If scale is greater than 1 if (scale > 1) { // Minus translateX by (eWidth / scale - vbWidth) / 2 @@ -68,30 +67,32 @@ + (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSSt } else if (![align isEqualToString: @"none"] && meetOrSlice == kRNSVGVBMOSSlice) { scaleX = scaleY = fmax(scaleX, scaleY); } - - // If align contains 'xMid', minus (e-width / scale-x - vb-width) / 2 from transform-x. + + // If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x. if ([align containsString:@"xMid"]) { - translateX -= (eWidth / scaleX - vbWidth) / 2; + translateX += (eWidth - vbWidth * scaleX) / 2.0; } - - // If align contains 'xMax', minus (e-width / scale-x - vb-width) from transform-x. + + // If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x. if ([align containsString:@"xMax"]) { - translateX -= eWidth / scaleX - vbWidth; + translateX += (eWidth - vbWidth * scaleX); } - - // If align contains 'yMid', minus (e-height / scale-y - vb-height) / 2 from transform-y. + + // If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y. if ([align containsString:@"YMid"]) { - translateY -= (eHeight / scaleY - vbHeight) / 2; + translateY += (eHeight - vbHeight * scaleY) / 2.0; } - - // If align contains 'yMax', minus (e-height / scale-y - vb-height) from transform-y. + + // If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y. if ([align containsString:@"YMax"]) { - translateY -= eHeight / scaleY - vbHeight; + translateY += (eHeight - vbHeight * scaleY); } } - + + // The transform applied to content contained by the element is given by + // translate(translate-x, translate-y) scale(scale-x, scale-y). CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY); - return CGAffineTransformTranslate(transform, -translateX * (fromSymbol ? scaleX : 1), -translateY * (fromSymbol ? scaleY : 1)); + return CGAffineTransformTranslate(transform, translateX, translateY); } @end From a2c1e0e4f3d24430bfc773c84a2ed15fbcd991c7 Mon Sep 17 00:00:00 2001 From: Petar Ivanov Date: Wed, 16 Aug 2017 16:49:43 -0700 Subject: [PATCH 175/198] fix broken multi touch for Android (#432) --- android/src/main/java/com/horcrux/svg/SvgView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index f2ccb1b3f..aede28687 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -148,6 +148,7 @@ public void handleTouchEvent(MotionEvent ev) { // this gesture. dispatch(ev, TouchEventType.END); mTargetTag = -1; + mGestureStartTime = TouchEvent.UNSET; } else if (action == MotionEvent.ACTION_MOVE) { // Update pointer position for current gesture dispatch(ev, TouchEventType.MOVE); @@ -157,8 +158,6 @@ public void handleTouchEvent(MotionEvent ev) { } else if (action == MotionEvent.ACTION_POINTER_UP) { // Exactly onw of the pointers goes up dispatch(ev, TouchEventType.END); - mTargetTag = -1; - mGestureStartTime = TouchEvent.UNSET; } else if (action == MotionEvent.ACTION_CANCEL) { dispatchCancelEvent(ev); mTargetTag = -1; From ccb87299171e50b4cc1f3c36d57acbfa60df46f3 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 23 Aug 2017 05:41:35 +0300 Subject: [PATCH 176/198] Port new GlyphContext, FontData, enums, props, Bezier and text rendering --- ios/Elements/RNSVGGroup.h | 4 +- ios/Elements/RNSVGGroup.m | 18 +- ios/PerformanceBezier/.gitignore | 26 + ios/PerformanceBezier/LICENSE | 319 ++++++++ .../project.pbxproj | 724 ++++++++++++++++++ .../PerformanceBezier/Info.plist | 22 + .../PerformanceBezier/JRSwizzle.h | 13 + .../PerformanceBezier/JRSwizzle.m | 134 ++++ .../PerformanceBezier-Info.plist | 5 + .../PerformanceBezier/PerformanceBezier.h | 20 + .../PerformanceBezier/UIBezierPath+Center.h | 17 + .../PerformanceBezier/UIBezierPath+Center.m | 18 + .../UIBezierPath+Clockwise.h | 18 + .../UIBezierPath+Clockwise.m | 50 ++ .../UIBezierPath+Description.h | 13 + .../UIBezierPath+Description.m | 57 ++ .../PerformanceBezier/UIBezierPath+Equals.h | 18 + .../PerformanceBezier/UIBezierPath+Equals.m | 18 + .../UIBezierPath+FirstLast.h | 21 + .../UIBezierPath+FirstLast.m | 61 ++ .../PerformanceBezier/UIBezierPath+NSOSX.h | 54 ++ .../PerformanceBezier/UIBezierPath+NSOSX.m | 522 +++++++++++++ .../UIBezierPath+NSOSX_Private.h | 29 + .../UIBezierPath+Performance.h | 49 ++ .../UIBezierPath+Performance.m | 525 +++++++++++++ .../UIBezierPath+Performance_Private.h | 26 + .../PerformanceBezier/UIBezierPath+Trim.h | 35 + .../PerformanceBezier/UIBezierPath+Trim.m | 209 +++++ .../PerformanceBezier/UIBezierPath+Trimming.h | 33 + .../PerformanceBezier/UIBezierPath+Trimming.m | 365 +++++++++ .../PerformanceBezier/UIBezierPath+Uncached.h | 17 + .../PerformanceBezier/UIBezierPath+Uncached.m | 23 + .../PerformanceBezier/UIBezierPath+Util.h | 48 ++ .../PerformanceBezier/UIBezierPath+Util.m | 316 ++++++++ .../UIBezierPathProperties.h | 25 + .../UIBezierPathProperties.m | 81 ++ .../PerformanceBezierTests/Info.plist | 24 + .../PerformanceBezierAbstractTest.h | 23 + .../PerformanceBezierAbstractTest.m | 161 ++++ .../PerformanceBezierClockwiseTests.m | 189 +++++ ios/PerformanceBezier/README.md | 67 ++ ios/PerformanceBezier/SubdivideLicense | 32 + ios/QuartzBookPack/Bezier/Bezier.h | 17 + ios/QuartzBookPack/Bezier/BezierElement.h | 34 + ios/QuartzBookPack/Bezier/BezierElement.m | 163 ++++ ios/QuartzBookPack/Bezier/BezierFunctions.h | 38 + ios/QuartzBookPack/Bezier/BezierFunctions.m | 205 +++++ ios/QuartzBookPack/Bezier/BezierUtils.h | 87 +++ ios/QuartzBookPack/Bezier/BezierUtils.m | 709 +++++++++++++++++ .../Bezier/UIBezierPath+Elements.h | 56 ++ .../Bezier/UIBezierPath+Elements.m | 519 +++++++++++++ ios/QuartzBookPack/Drawing/Drawing-Block.h | 22 + ios/QuartzBookPack/Drawing/Drawing-Block.m | 71 ++ ios/QuartzBookPack/Drawing/Drawing-Gradient.h | 44 ++ ios/QuartzBookPack/Drawing/Drawing-Gradient.m | 256 +++++++ ios/QuartzBookPack/Drawing/Drawing-Util.h | 25 + ios/QuartzBookPack/Drawing/Drawing-Util.m | 241 ++++++ ios/QuartzBookPack/Geometry/BaseGeometry.h | 70 ++ ios/QuartzBookPack/Geometry/BaseGeometry.m | 242 ++++++ ios/QuartzBookPack/Image/ImageUtils.h | 40 + ios/QuartzBookPack/Image/ImageUtils.m | 372 +++++++++ ios/QuartzBookPack/TextDrawing/Drawing-Text.h | 23 + ios/QuartzBookPack/TextDrawing/Drawing-Text.m | 175 +++++ .../TextDrawing/UIBezierPath+Text.h | 13 + .../TextDrawing/UIBezierPath+Text.m | 56 ++ ios/QuartzBookPack/Utility/Utility.h | 48 ++ ios/QuartzBookPack/Utility/Utility.m | 240 ++++++ ios/RNSVG.xcodeproj/project.pbxproj | 297 ++++++- ios/RNSVGNode.m | 33 +- ios/Text/AlignmentBaseline.h | 63 ++ ios/Text/AlignmentBaseline.m | 18 + ios/Text/FontData.h | 43 ++ ios/Text/FontData.m | 108 +++ ios/Text/FontStyle.h | 19 + ios/Text/FontStyle.m | 18 + ios/Text/FontVariantLigatures.h | 18 + ios/Text/FontVariantLigatures.m | 18 + ios/Text/FontWeight.h | 30 + ios/Text/FontWeight.m | 18 + ios/Text/GlyphContext.h | 51 ++ ios/Text/GlyphContext.m | 437 +++++++++++ ios/Text/PropHelper.h | 15 + ios/Text/PropHelper.m | 49 ++ ios/Text/RNSVGTSpan.h | 9 +- ios/Text/RNSVGTSpan.m | 713 ++++++++++++++++- ios/Text/RNSVGText.h | 7 +- ios/Text/RNSVGText.m | 81 +- ios/Text/RNSVGTextPath.h | 5 + ios/Text/RNSVGTextPath.m | 8 +- ios/Text/TextAnchor.h | 19 + ios/Text/TextAnchor.m | 18 + ios/Text/TextDecoration.h | 21 + ios/Text/TextDecoration.m | 18 + ios/Text/TextLengthAdjust.h | 18 + ios/Text/TextLengthAdjust.m | 18 + ios/Text/TextPathMethod.h | 18 + ios/Text/TextPathMethod.m | 18 + ios/Text/TextPathMidLine.h | 18 + ios/Text/TextPathMidLine.m | 18 + ios/Text/TextPathSide.h | 18 + ios/Text/TextPathSide.m | 18 + ios/Text/TextPathSpacing.h | 18 + ios/Text/TextPathSpacing.m | 18 + .../UIBezierPath+getTransformAtDistance.h | 15 + .../UIBezierPath+getTransformAtDistance.m | 18 + 105 files changed, 10437 insertions(+), 105 deletions(-) create mode 100644 ios/PerformanceBezier/.gitignore create mode 100644 ios/PerformanceBezier/LICENSE create mode 100644 ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj create mode 100644 ios/PerformanceBezier/PerformanceBezier/Info.plist create mode 100644 ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist create mode 100644 ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h create mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m create mode 100644 ios/PerformanceBezier/PerformanceBezierTests/Info.plist create mode 100644 ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h create mode 100644 ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m create mode 100644 ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m create mode 100644 ios/PerformanceBezier/README.md create mode 100644 ios/PerformanceBezier/SubdivideLicense create mode 100644 ios/QuartzBookPack/Bezier/Bezier.h create mode 100644 ios/QuartzBookPack/Bezier/BezierElement.h create mode 100644 ios/QuartzBookPack/Bezier/BezierElement.m create mode 100644 ios/QuartzBookPack/Bezier/BezierFunctions.h create mode 100644 ios/QuartzBookPack/Bezier/BezierFunctions.m create mode 100644 ios/QuartzBookPack/Bezier/BezierUtils.h create mode 100644 ios/QuartzBookPack/Bezier/BezierUtils.m create mode 100644 ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h create mode 100644 ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m create mode 100644 ios/QuartzBookPack/Drawing/Drawing-Block.h create mode 100644 ios/QuartzBookPack/Drawing/Drawing-Block.m create mode 100644 ios/QuartzBookPack/Drawing/Drawing-Gradient.h create mode 100644 ios/QuartzBookPack/Drawing/Drawing-Gradient.m create mode 100644 ios/QuartzBookPack/Drawing/Drawing-Util.h create mode 100644 ios/QuartzBookPack/Drawing/Drawing-Util.m create mode 100644 ios/QuartzBookPack/Geometry/BaseGeometry.h create mode 100644 ios/QuartzBookPack/Geometry/BaseGeometry.m create mode 100644 ios/QuartzBookPack/Image/ImageUtils.h create mode 100644 ios/QuartzBookPack/Image/ImageUtils.m create mode 100644 ios/QuartzBookPack/TextDrawing/Drawing-Text.h create mode 100644 ios/QuartzBookPack/TextDrawing/Drawing-Text.m create mode 100644 ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h create mode 100644 ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m create mode 100644 ios/QuartzBookPack/Utility/Utility.h create mode 100644 ios/QuartzBookPack/Utility/Utility.m create mode 100644 ios/Text/AlignmentBaseline.h create mode 100644 ios/Text/AlignmentBaseline.m create mode 100644 ios/Text/FontData.h create mode 100644 ios/Text/FontData.m create mode 100644 ios/Text/FontStyle.h create mode 100644 ios/Text/FontStyle.m create mode 100644 ios/Text/FontVariantLigatures.h create mode 100644 ios/Text/FontVariantLigatures.m create mode 100644 ios/Text/FontWeight.h create mode 100644 ios/Text/FontWeight.m create mode 100644 ios/Text/GlyphContext.h create mode 100644 ios/Text/GlyphContext.m create mode 100644 ios/Text/PropHelper.h create mode 100644 ios/Text/PropHelper.m create mode 100644 ios/Text/TextAnchor.h create mode 100644 ios/Text/TextAnchor.m create mode 100644 ios/Text/TextDecoration.h create mode 100644 ios/Text/TextDecoration.m create mode 100644 ios/Text/TextLengthAdjust.h create mode 100644 ios/Text/TextLengthAdjust.m create mode 100644 ios/Text/TextPathMethod.h create mode 100644 ios/Text/TextPathMethod.m create mode 100644 ios/Text/TextPathMidLine.h create mode 100644 ios/Text/TextPathMidLine.m create mode 100644 ios/Text/TextPathSide.h create mode 100644 ios/Text/TextPathSide.m create mode 100644 ios/Text/TextPathSpacing.h create mode 100644 ios/Text/TextPathSpacing.m create mode 100644 ios/Utils/UIBezierPath+getTransformAtDistance.h create mode 100644 ios/Utils/UIBezierPath+getTransformAtDistance.m diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index 163d8477f..11576f0a1 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -12,6 +12,7 @@ #import "RNSVGCGFCRule.h" #import "RNSVGSvgView.h" #import "RNSVGPath.h" +#import "GlyphContext.h" #import "RNSVGGlyphContext.h" @interface RNSVGGroup : RNSVGPath @@ -21,7 +22,8 @@ - (void)renderPathTo:(CGContextRef)context; - (void)renderGroupTo:(CGContextRef)context; -- (RNSVGGlyphContext *)getGlyphContext; +- (RNSVGGlyphContext *)getRNSVGGlyphContext; +- (GlyphContext *)getGlyphContext; - (void)pushGlyphContext; - (void)popGlyphContext; @end diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index c093e90c0..8e4d5540e 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -10,7 +10,8 @@ @implementation RNSVGGroup { - RNSVGGlyphContext *_glyphContext; + GlyphContext *_glyphContext; + RNSVGGlyphContext *_RNSVGGlyphContext; } - (void)renderLayerTo:(CGContextRef)context @@ -51,22 +52,31 @@ - (void)setupGlyphContext:(CGContextRef)context CGFloat width = CGRectGetWidth(clipBounds); CGFloat height = CGRectGetHeight(clipBounds); - _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:width + _RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:width height:height]; + _glyphContext = [[GlyphContext alloc] initWithScale:1 width:width + height:height]; +} + +- (RNSVGGlyphContext *)getRNSVGGlyphContext +{ + return _RNSVGGlyphContext; } -- (RNSVGGlyphContext *)getGlyphContext +- (GlyphContext *)getGlyphContext { return _glyphContext; } - (void)pushGlyphContext { - [[[self getTextRoot] getGlyphContext] pushContext:self.font]; + [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font]; + [[[self getTextRoot] getGlyphContext] pushContextWithRNSVGGroup:self font:self.font]; } - (void)popGlyphContext { + [[[self getTextRoot] getRNSVGGlyphContext] popContext]; [[[self getTextRoot] getGlyphContext] popContext]; } diff --git a/ios/PerformanceBezier/.gitignore b/ios/PerformanceBezier/.gitignore new file mode 100644 index 000000000..a3cd143a4 --- /dev/null +++ b/ios/PerformanceBezier/.gitignore @@ -0,0 +1,26 @@ +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Pods/ diff --git a/ios/PerformanceBezier/LICENSE b/ios/PerformanceBezier/LICENSE new file mode 100644 index 000000000..a7fe54e41 --- /dev/null +++ b/ios/PerformanceBezier/LICENSE @@ -0,0 +1,319 @@ +Creative Commons Legal Code + +Attribution 3.0 Unported + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN +ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS +INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES +REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR +DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + +a. "Adaptation" means a work based upon the Work, or upon the Work and +other pre-existing works, such as a translation, adaptation, +derivative work, arrangement of music or other alterations of a +literary or artistic work, or phonogram or performance and includes +cinematographic adaptations or any other form in which the Work may be +recast, transformed, or adapted including in any form recognizably +derived from the original, except that a work that constitutes a +Collection will not be considered an Adaptation for the purpose of +this License. For the avoidance of doubt, where the Work is a musical +work, performance or phonogram, the synchronization of the Work in +timed-relation with a moving image ("synching") will be considered an +Adaptation for the purpose of this License. +b. "Collection" means a collection of literary or artistic works, such as +encyclopedias and anthologies, or performances, phonograms or +broadcasts, or other works or subject matter other than works listed +in Section 1(f) below, which, by reason of the selection and +arrangement of their contents, constitute intellectual creations, in +which the Work is included in its entirety in unmodified form along +with one or more other contributions, each constituting separate and +independent works in themselves, which together are assembled into a +collective whole. A work that constitutes a Collection will not be +considered an Adaptation (as defined above) for the purposes of this +License. +c. "Distribute" means to make available to the public the original and +copies of the Work or Adaptation, as appropriate, through sale or +other transfer of ownership. +d. "Licensor" means the individual, individuals, entity or entities that +offer(s) the Work under the terms of this License. +e. "Original Author" means, in the case of a literary or artistic work, +the individual, individuals, entity or entities who created the Work +or if no individual or entity can be identified, the publisher; and in +addition (i) in the case of a performance the actors, singers, +musicians, dancers, and other persons who act, sing, deliver, declaim, +play in, interpret or otherwise perform literary or artistic works or +expressions of folklore; (ii) in the case of a phonogram the producer +being the person or legal entity who first fixes the sounds of a +performance or other sounds; and, (iii) in the case of broadcasts, the +organization that transmits the broadcast. +f. "Work" means the literary and/or artistic work offered under the terms +of this License including without limitation any production in the +literary, scientific and artistic domain, whatever may be the mode or +form of its expression including digital form, such as a book, +pamphlet and other writing; a lecture, address, sermon or other work +of the same nature; a dramatic or dramatico-musical work; a +choreographic work or entertainment in dumb show; a musical +composition with or without words; a cinematographic work to which are +assimilated works expressed by a process analogous to cinematography; +a work of drawing, painting, architecture, sculpture, engraving or +lithography; a photographic work to which are assimilated works +expressed by a process analogous to photography; a work of applied +art; an illustration, map, plan, sketch or three-dimensional work +relative to geography, topography, architecture or science; a +performance; a broadcast; a phonogram; a compilation of data to the +extent it is protected as a copyrightable work; or a work performed by +a variety or circus performer to the extent it is not otherwise +considered a literary or artistic work. +g. "You" means an individual or entity exercising rights under this +License who has not previously violated the terms of this License with +respect to the Work, or who has received express permission from the +Licensor to exercise rights under this License despite a previous +violation. +h. "Publicly Perform" means to perform public recitations of the Work and +to communicate to the public those public recitations, by any means or +process, including by wire or wireless means or public digital +performances; to make available to the public Works in such a way that +members of the public may access these Works from a place and at a +place individually chosen by them; to perform the Work to the public +by any means or process and the communication to the public of the +performances of the Work, including by public digital performance; to +broadcast and rebroadcast the Work by any means including signs, +sounds or images. +i. "Reproduce" means to make copies of the Work by any means including +without limitation by sound or visual recordings and the right of +fixation and reproducing fixations of the Work, including storage of a +protected performance or phonogram in digital form or other electronic +medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + +a. to Reproduce the Work, to incorporate the Work into one or more +Collections, and to Reproduce the Work as incorporated in the +Collections; +b. to create and Reproduce Adaptations provided that any such Adaptation, +including any translation in any medium, takes reasonable steps to +clearly label, demarcate or otherwise identify that changes were made +to the original Work. For example, a translation could be marked "The +original work was translated from English to Spanish," or a +modification could indicate "The original work has been modified."; +c. to Distribute and Publicly Perform the Work including as incorporated +in Collections; and, +d. to Distribute and Publicly Perform Adaptations. +e. For the avoidance of doubt: + +i. Non-waivable Compulsory License Schemes. In those jurisdictions in +which the right to collect royalties through any statutory or +compulsory licensing scheme cannot be waived, the Licensor +reserves the exclusive right to collect such royalties for any +exercise by You of the rights granted under this License; +ii. Waivable Compulsory License Schemes. In those jurisdictions in +which the right to collect royalties through any statutory or +compulsory licensing scheme can be waived, the Licensor waives the +exclusive right to collect such royalties for any exercise by You +of the rights granted under this License; and, +iii. Voluntary License Schemes. The Licensor waives the right to +collect royalties, whether individually or, in the event that the +Licensor is a member of a collecting society that administers +voluntary licensing schemes, via that society, from any exercise +by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights in +other media and formats. Subject to Section 8(f), all rights not expressly +granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + +a. You may Distribute or Publicly Perform the Work only under the terms +of this License. You must include a copy of, or the Uniform Resource +Identifier (URI) for, this License with every copy of the Work You +Distribute or Publicly Perform. You may not offer or impose any terms +on the Work that restrict the terms of this License or the ability of +the recipient of the Work to exercise the rights granted to that +recipient under the terms of the License. You may not sublicense the +Work. You must keep intact all notices that refer to this License and +to the disclaimer of warranties with every copy of the Work You +Distribute or Publicly Perform. When You Distribute or Publicly +Perform the Work, You may not impose any effective technological +measures on the Work that restrict the ability of a recipient of the +Work from You to exercise the rights granted to that recipient under +the terms of the License. This Section 4(a) applies to the Work as +incorporated in a Collection, but this does not require the Collection +apart from the Work itself to be made subject to the terms of this +License. If You create a Collection, upon notice from any Licensor You +must, to the extent practicable, remove from the Collection any credit +as required by Section 4(b), as requested. If You create an +Adaptation, upon notice from any Licensor You must, to the extent +practicable, remove from the Adaptation any credit as required by +Section 4(b), as requested. +b. If You Distribute, or Publicly Perform the Work or any Adaptations or +Collections, You must, unless a request has been made pursuant to +Section 4(a), keep intact all copyright notices for the Work and +provide, reasonable to the medium or means You are utilizing: (i) the +name of the Original Author (or pseudonym, if applicable) if supplied, +and/or if the Original Author and/or Licensor designate another party +or parties (e.g., a sponsor institute, publishing entity, journal) for +attribution ("Attribution Parties") in Licensor's copyright notice, +terms of service or by other reasonable means, the name of such party +or parties; (ii) the title of the Work if supplied; (iii) to the +extent reasonably practicable, the URI, if any, that Licensor +specifies to be associated with the Work, unless such URI does not +refer to the copyright notice or licensing information for the Work; +and (iv) , consistent with Section 3(b), in the case of an Adaptation, +a credit identifying the use of the Work in the Adaptation (e.g., +"French translation of the Work by Original Author," or "Screenplay +based on original Work by Original Author"). The credit required by +this Section 4 (b) may be implemented in any reasonable manner; +provided, however, that in the case of a Adaptation or Collection, at +a minimum such credit will appear, if a credit for all contributing +authors of the Adaptation or Collection appears, then as part of these +credits and in a manner at least as prominent as the credits for the +other contributing authors. For the avoidance of doubt, You may only +use the credit required by this Section for the purpose of attribution +in the manner set out above and, by exercising Your rights under this +License, You may not implicitly or explicitly assert or imply any +connection with, sponsorship or endorsement by the Original Author, +Licensor and/or Attribution Parties, as appropriate, of You or Your +use of the Work, without the separate, express prior written +permission of the Original Author, Licensor and/or Attribution +Parties. +c. Except as otherwise agreed in writing by the Licensor or as may be +otherwise permitted by applicable law, if You Reproduce, Distribute or +Publicly Perform the Work either by itself or as part of any +Adaptations or Collections, You must not distort, mutilate, modify or +take other derogatory action in relation to the Work which would be +prejudicial to the Original Author's honor or reputation. Licensor +agrees that in those jurisdictions (e.g. Japan), in which any exercise +of the right granted in Section 3(b) of this License (the right to +make Adaptations) would be deemed to be a distortion, mutilation, +modification or other derogatory action prejudicial to the Original +Author's honor and reputation, the Licensor will waive or not assert, +as appropriate, this Section, to the fullest extent permitted by the +applicable national law, to enable You to reasonably exercise Your +right under Section 3(b) of this License (right to make Adaptations) +but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + +a. This License and the rights granted hereunder will terminate +automatically upon any breach by You of the terms of this License. +Individuals or entities who have received Adaptations or Collections +from You under this License, however, will not have their licenses +terminated provided such individuals or entities remain in full +compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will +survive any termination of this License. +b. Subject to the above terms and conditions, the license granted here is +perpetual (for the duration of the applicable copyright in the Work). +Notwithstanding the above, Licensor reserves the right to release the +Work under different license terms or to stop distributing the Work at +any time; provided, however that any such election will not serve to +withdraw this License (or any other license that has been, or is +required to be, granted under the terms of this License), and this +License will continue in full force and effect unless terminated as +stated above. + +8. Miscellaneous + +a. Each time You Distribute or Publicly Perform the Work or a Collection, +the Licensor offers to the recipient a license to the Work on the same +terms and conditions as the license granted to You under this License. +b. Each time You Distribute or Publicly Perform an Adaptation, Licensor +offers to the recipient a license to the original Work on the same +terms and conditions as the license granted to You under this License. +c. If any provision of this License is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this License, and without further action +by the parties to this agreement, such provision shall be reformed to +the minimum extent necessary to make such provision valid and +enforceable. +d. No term or provision of this License shall be deemed waived and no +breach consented to unless such waiver or consent shall be in writing +and signed by the party to be charged with such waiver or consent. +e. This License constitutes the entire agreement between the parties with +respect to the Work licensed here. There are no understandings, +agreements or representations with respect to the Work not specified +here. Licensor shall not be bound by any additional provisions that +may appear in any communication from You. This License may not be +modified without the mutual written agreement of the Licensor and You. +f. The rights granted under, and the subject matter referenced, in this +License were drafted utilizing the terminology of the Berne Convention +for the Protection of Literary and Artistic Works (as amended on +September 28, 1979), the Rome Convention of 1961, the WIPO Copyright +Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 +and the Universal Copyright Convention (as revised on July 24, 1971). +These rights and subject matter take effect in the relevant +jurisdiction in which the License terms are sought to be enforced +according to the corresponding provisions of the implementation of +those treaty provisions in the applicable national law. If the +standard suite of rights granted under applicable copyright law +includes additional rights not granted under this License, such +additional rights are deemed to be included in the License; this +License is not intended to restrict the license of any rights under +applicable law. + + +Creative Commons Notice + +Creative Commons is not a party to this License, and makes no warranty +whatsoever in connection with the Work. Creative Commons will not be +liable to You or any party on any legal theory for any damages +whatsoever, including without limitation any general, special, +incidental or consequential damages arising in connection to this +license. Notwithstanding the foregoing two (2) sentences, if Creative +Commons has expressly identified itself as the Licensor hereunder, it +shall have all rights and obligations of Licensor. + +Except for the limited purpose of indicating to the public that the +Work is licensed under the CCPL, Creative Commons does not authorize +the use by either party of the trademark "Creative Commons" or any +related trademark or logo of Creative Commons without the prior +written consent of Creative Commons. Any permitted use will be in +compliance with Creative Commons' then-current trademark usage +guidelines, as may be published on its website or otherwise made +available upon request from time to time. For the avoidance of doubt, +this trademark restriction does not form part of this License. + +Creative Commons may be contacted at https://creativecommons.org/. \ No newline at end of file diff --git a/ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj b/ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj new file mode 100644 index 000000000..1148c4849 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj @@ -0,0 +1,724 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 6617530D1A8DC45A0051D5CB /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */; }; + 66AB566C1B0D9830005E6FB1 /* UIBezierPath+Util.h in Headers */ = {isa = PBXBuildFile; fileRef = 66AB566A1B0D9830005E6FB1 /* UIBezierPath+Util.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66AB566D1B0D9830005E6FB1 /* UIBezierPath+Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 66AB566B1B0D9830005E6FB1 /* UIBezierPath+Util.m */; }; + 66B9D2A61A8D609200CAC341 /* PerformanceBezierAbstractTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 66D4021F1A7EDACA00043802 /* PerformanceBezierAbstractTest.m */; }; + 66B9D2A71A8D609300CAC341 /* PerformanceBezierClockwiseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 66D4021C1A7EDA9000043802 /* PerformanceBezierClockwiseTests.m */; }; + 66D1B7971AFEAC6F00210262 /* UIBezierPath+Trim.h in Headers */ = {isa = PBXBuildFile; fileRef = 66D1B7951AFEAC6F00210262 /* UIBezierPath+Trim.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66D1B7981AFEAC6F00210262 /* UIBezierPath+Trim.m in Sources */ = {isa = PBXBuildFile; fileRef = 66D1B7961AFEAC6F00210262 /* UIBezierPath+Trim.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 66F2EBE41A8DC05100D536E9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6682FF251A8DBACF00187325 /* Foundation.framework */; }; + 66F2EBF61A8DC12600D536E9 /* UIBezierPath+Center.m in Sources */ = {isa = PBXBuildFile; fileRef = 660E0FD81A80055300F19D8A /* UIBezierPath+Center.m */; }; + 66F2EBF71A8DC12800D536E9 /* UIBezierPath+Clockwise.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617D01A7EB88200C0B447 /* UIBezierPath+Clockwise.m */; }; + 66F2EBF81A8DC13100D536E9 /* UIBezierPath+Equals.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617CC1A7EB86200C0B447 /* UIBezierPath+Equals.m */; }; + 66F2EBF91A8DC13300D536E9 /* UIBezierPath+Description.m in Sources */ = {isa = PBXBuildFile; fileRef = 663355CC1A8575F600C45718 /* UIBezierPath+Description.m */; }; + 66F2EBFA1A8DC13500D536E9 /* UIBezierPath+NSOSX.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617C11A7EB79F00C0B447 /* UIBezierPath+NSOSX.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 66F2EBFB1A8DC13700D536E9 /* UIBezierPath+Performance.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617F61A7EC58700C0B447 /* UIBezierPath+Performance.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 66F2EBFC1A8DC13900D536E9 /* UIBezierPath+Uncached.m in Sources */ = {isa = PBXBuildFile; fileRef = 663355D61A857A2700C45718 /* UIBezierPath+Uncached.m */; }; + 66F2EBFD1A8DC13D00D536E9 /* UIBezierPath+FirstLast.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617FB1A7EC5D400C0B447 /* UIBezierPath+FirstLast.m */; }; + 66F2EBFE1A8DC13F00D536E9 /* UIBezierPathProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617B81A7EB73A00C0B447 /* UIBezierPathProperties.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 66F2EBFF1A8DC14100D536E9 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617D41A7EB9AC00C0B447 /* JRSwizzle.m */; }; + 66F2EC001A8DC15600D536E9 /* UIBezierPath+Uncached.h in Headers */ = {isa = PBXBuildFile; fileRef = 663355D51A857A2700C45718 /* UIBezierPath+Uncached.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC011A8DC15800D536E9 /* UIBezierPath+Performance.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617F41A7EC58700C0B447 /* UIBezierPath+Performance.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC021A8DC15A00D536E9 /* UIBezierPath+NSOSX.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617C01A7EB79F00C0B447 /* UIBezierPath+NSOSX.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC031A8DC15B00D536E9 /* UIBezierPath+Description.h in Headers */ = {isa = PBXBuildFile; fileRef = 663355CB1A8575F600C45718 /* UIBezierPath+Description.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC041A8DC15E00D536E9 /* UIBezierPath+Equals.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617CB1A7EB86200C0B447 /* UIBezierPath+Equals.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC051A8DC16000D536E9 /* UIBezierPath+Clockwise.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617CF1A7EB88200C0B447 /* UIBezierPath+Clockwise.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC061A8DC16100D536E9 /* UIBezierPath+Center.h in Headers */ = {isa = PBXBuildFile; fileRef = 660E0FD71A80055300F19D8A /* UIBezierPath+Center.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC071A8DC16300D536E9 /* UIBezierPath+Performance_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617F51A7EC58700C0B447 /* UIBezierPath+Performance_Private.h */; }; + 66F2EC081A8DC16500D536E9 /* UIBezierPath+NSOSX_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617BF1A7EB79F00C0B447 /* UIBezierPath+NSOSX_Private.h */; }; + 66F2EC091A8DC16600D536E9 /* UIBezierPath+FirstLast.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617FA1A7EC5D400C0B447 /* UIBezierPath+FirstLast.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC0A1A8DC16800D536E9 /* UIBezierPathProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617B71A7EB73A00C0B447 /* UIBezierPathProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC0B1A8DC16900D536E9 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617D31A7EB9AC00C0B447 /* JRSwizzle.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC0C1A8DC16B00D536E9 /* PerformanceBezier.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617AF1A7EB5CB00C0B447 /* PerformanceBezier.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66F2EC0D1A8DC22000D536E9 /* PerformanceBezier-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 66F2EBEA1A8DC05100D536E9 /* PerformanceBezier-Info.plist */; }; + 9494C4A01F4715A000D5BCFD /* UIBezierPath+Trimming.h in Headers */ = {isa = PBXBuildFile; fileRef = 9494C49E1F4715A000D5BCFD /* UIBezierPath+Trimming.h */; }; + 9494C4A11F4715A000D5BCFD /* UIBezierPath+Trimming.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C49F1F4715A000D5BCFD /* UIBezierPath+Trimming.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6617530E1A8DC8300051D5CB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 661B30C91A7EB43A008549C7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 66F2EBE21A8DC05100D536E9; + remoteInfo = PerformanceBezier; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 660E0FD71A80055300F19D8A /* UIBezierPath+Center.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Center.h"; sourceTree = ""; }; + 660E0FD81A80055300F19D8A /* UIBezierPath+Center.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Center.m"; sourceTree = ""; }; + 660E0FDB1A80226100F19D8A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 660E0FDC1A80284C00F19D8A /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 663355CB1A8575F600C45718 /* UIBezierPath+Description.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Description.h"; sourceTree = ""; }; + 663355CC1A8575F600C45718 /* UIBezierPath+Description.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Description.m"; sourceTree = ""; }; + 663355D51A857A2700C45718 /* UIBezierPath+Uncached.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Uncached.h"; sourceTree = ""; }; + 663355D61A857A2700C45718 /* UIBezierPath+Uncached.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Uncached.m"; sourceTree = ""; }; + 667617AF1A7EB5CB00C0B447 /* PerformanceBezier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformanceBezier.h; sourceTree = ""; }; + 667617B71A7EB73A00C0B447 /* UIBezierPathProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIBezierPathProperties.h; sourceTree = ""; }; + 667617B81A7EB73A00C0B447 /* UIBezierPathProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIBezierPathProperties.m; sourceTree = ""; }; + 667617BF1A7EB79F00C0B447 /* UIBezierPath+NSOSX_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+NSOSX_Private.h"; sourceTree = ""; }; + 667617C01A7EB79F00C0B447 /* UIBezierPath+NSOSX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+NSOSX.h"; sourceTree = ""; }; + 667617C11A7EB79F00C0B447 /* UIBezierPath+NSOSX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+NSOSX.m"; sourceTree = ""; }; + 667617CB1A7EB86200C0B447 /* UIBezierPath+Equals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Equals.h"; sourceTree = ""; }; + 667617CC1A7EB86200C0B447 /* UIBezierPath+Equals.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Equals.m"; sourceTree = ""; }; + 667617CF1A7EB88200C0B447 /* UIBezierPath+Clockwise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Clockwise.h"; sourceTree = ""; }; + 667617D01A7EB88200C0B447 /* UIBezierPath+Clockwise.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Clockwise.m"; sourceTree = ""; }; + 667617D31A7EB9AC00C0B447 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = ""; }; + 667617D41A7EB9AC00C0B447 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = ""; }; + 667617F41A7EC58700C0B447 /* UIBezierPath+Performance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Performance.h"; sourceTree = ""; }; + 667617F51A7EC58700C0B447 /* UIBezierPath+Performance_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Performance_Private.h"; sourceTree = ""; }; + 667617F61A7EC58700C0B447 /* UIBezierPath+Performance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Performance.m"; sourceTree = ""; }; + 667617FA1A7EC5D400C0B447 /* UIBezierPath+FirstLast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+FirstLast.h"; sourceTree = ""; }; + 667617FB1A7EC5D400C0B447 /* UIBezierPath+FirstLast.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+FirstLast.m"; sourceTree = ""; }; + 66767CC71AFEE4BB00443B03 /* SubdivideLicense */ = {isa = PBXFileReference; lastKnownFileType = text; path = SubdivideLicense; sourceTree = ""; }; + 6682FF251A8DBACF00187325 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 66AB566A1B0D9830005E6FB1 /* UIBezierPath+Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Util.h"; sourceTree = ""; }; + 66AB566B1B0D9830005E6FB1 /* UIBezierPath+Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Util.m"; sourceTree = ""; }; + 66B9D28C1A8D5FDE00CAC341 /* PerformanceBezierTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerformanceBezierTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66B9D2921A8D5FDF00CAC341 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 66D1B7951AFEAC6F00210262 /* UIBezierPath+Trim.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Trim.h"; sourceTree = ""; }; + 66D1B7961AFEAC6F00210262 /* UIBezierPath+Trim.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Trim.m"; sourceTree = ""; }; + 66D4021C1A7EDA9000043802 /* PerformanceBezierClockwiseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceBezierClockwiseTests.m; sourceTree = ""; }; + 66D4021E1A7EDACA00043802 /* PerformanceBezierAbstractTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PerformanceBezierAbstractTest.h; sourceTree = ""; }; + 66D4021F1A7EDACA00043802 /* PerformanceBezierAbstractTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceBezierAbstractTest.m; sourceTree = ""; }; + 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework.static; includeInIndex = 0; path = PerformanceBezier.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 66F2EBE71A8DC05100D536E9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 66F2EBEA1A8DC05100D536E9 /* PerformanceBezier-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PerformanceBezier-Info.plist"; sourceTree = ""; }; + 9494C49E1F4715A000D5BCFD /* UIBezierPath+Trimming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Trimming.h"; sourceTree = ""; }; + 9494C49F1F4715A000D5BCFD /* UIBezierPath+Trimming.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Trimming.m"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 66B9D2891A8D5FDE00CAC341 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6617530D1A8DC45A0051D5CB /* PerformanceBezier.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66F2EBDE1A8DC05100D536E9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 66F2EBE41A8DC05100D536E9 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 661B30C81A7EB43A008549C7 = { + isa = PBXGroup; + children = ( + 660E0FDB1A80226100F19D8A /* README.md */, + 66767CC71AFEE4BB00443B03 /* SubdivideLicense */, + 660E0FDC1A80284C00F19D8A /* LICENSE */, + 66B9D2831A8D5FDE00CAC341 /* PerformanceBezier */, + 66B9D2901A8D5FDF00CAC341 /* PerformanceBezierTests */, + 66F2EBE51A8DC05100D536E9 /* Supporting Files */, + 6682FF241A8DBACF00187325 /* Frameworks */, + 667617871A7EB50A00C0B447 /* Products */, + ); + sourceTree = ""; + }; + 667617871A7EB50A00C0B447 /* Products */ = { + isa = PBXGroup; + children = ( + 66B9D28C1A8D5FDE00CAC341 /* PerformanceBezierTests.xctest */, + 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */, + ); + name = Products; + sourceTree = ""; + }; + 667617B61A7EB72F00C0B447 /* Categories */ = { + isa = PBXGroup; + children = ( + 9494C49E1F4715A000D5BCFD /* UIBezierPath+Trimming.h */, + 9494C49F1F4715A000D5BCFD /* UIBezierPath+Trimming.m */, + 660E0FD71A80055300F19D8A /* UIBezierPath+Center.h */, + 660E0FD81A80055300F19D8A /* UIBezierPath+Center.m */, + 667617CF1A7EB88200C0B447 /* UIBezierPath+Clockwise.h */, + 667617D01A7EB88200C0B447 /* UIBezierPath+Clockwise.m */, + 667617CB1A7EB86200C0B447 /* UIBezierPath+Equals.h */, + 667617CC1A7EB86200C0B447 /* UIBezierPath+Equals.m */, + 663355CB1A8575F600C45718 /* UIBezierPath+Description.h */, + 663355CC1A8575F600C45718 /* UIBezierPath+Description.m */, + 667617C01A7EB79F00C0B447 /* UIBezierPath+NSOSX.h */, + 667617C11A7EB79F00C0B447 /* UIBezierPath+NSOSX.m */, + 667617F41A7EC58700C0B447 /* UIBezierPath+Performance.h */, + 667617F61A7EC58700C0B447 /* UIBezierPath+Performance.m */, + 66D1B7951AFEAC6F00210262 /* UIBezierPath+Trim.h */, + 66D1B7961AFEAC6F00210262 /* UIBezierPath+Trim.m */, + 663355D51A857A2700C45718 /* UIBezierPath+Uncached.h */, + 663355D61A857A2700C45718 /* UIBezierPath+Uncached.m */, + 66AB566A1B0D9830005E6FB1 /* UIBezierPath+Util.h */, + 66AB566B1B0D9830005E6FB1 /* UIBezierPath+Util.m */, + ); + name = Categories; + sourceTree = ""; + }; + 667617DB1A7EB9B000C0B447 /* Protected */ = { + isa = PBXGroup; + children = ( + 667617D31A7EB9AC00C0B447 /* JRSwizzle.h */, + 667617D41A7EB9AC00C0B447 /* JRSwizzle.m */, + 667617B71A7EB73A00C0B447 /* UIBezierPathProperties.h */, + 667617B81A7EB73A00C0B447 /* UIBezierPathProperties.m */, + 667617FA1A7EC5D400C0B447 /* UIBezierPath+FirstLast.h */, + 667617FB1A7EC5D400C0B447 /* UIBezierPath+FirstLast.m */, + 667617BF1A7EB79F00C0B447 /* UIBezierPath+NSOSX_Private.h */, + 667617F51A7EC58700C0B447 /* UIBezierPath+Performance_Private.h */, + ); + name = Protected; + sourceTree = ""; + }; + 6682FF241A8DBACF00187325 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6682FF251A8DBACF00187325 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 66B9D2831A8D5FDE00CAC341 /* PerformanceBezier */ = { + isa = PBXGroup; + children = ( + 667617AF1A7EB5CB00C0B447 /* PerformanceBezier.h */, + 667617DB1A7EB9B000C0B447 /* Protected */, + 667617B61A7EB72F00C0B447 /* Categories */, + ); + path = PerformanceBezier; + sourceTree = ""; + }; + 66B9D2901A8D5FDF00CAC341 /* PerformanceBezierTests */ = { + isa = PBXGroup; + children = ( + 66D4021E1A7EDACA00043802 /* PerformanceBezierAbstractTest.h */, + 66D4021F1A7EDACA00043802 /* PerformanceBezierAbstractTest.m */, + 66D4021C1A7EDA9000043802 /* PerformanceBezierClockwiseTests.m */, + 66B9D2911A8D5FDF00CAC341 /* Supporting Files */, + ); + path = PerformanceBezierTests; + sourceTree = ""; + }; + 66B9D2911A8D5FDF00CAC341 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 66B9D2921A8D5FDF00CAC341 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 66F2EBE51A8DC05100D536E9 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 66F2EBEA1A8DC05100D536E9 /* PerformanceBezier-Info.plist */, + 66F2EBE71A8DC05100D536E9 /* Info.plist */, + ); + name = "Supporting Files"; + path = PerformanceBezier; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 66F2EBDF1A8DC05100D536E9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 66F2EC0C1A8DC16B00D536E9 /* PerformanceBezier.h in Headers */, + 66F2EC001A8DC15600D536E9 /* UIBezierPath+Uncached.h in Headers */, + 66F2EC0B1A8DC16900D536E9 /* JRSwizzle.h in Headers */, + 66F2EC011A8DC15800D536E9 /* UIBezierPath+Performance.h in Headers */, + 66F2EC0A1A8DC16800D536E9 /* UIBezierPathProperties.h in Headers */, + 66F2EC041A8DC15E00D536E9 /* UIBezierPath+Equals.h in Headers */, + 66F2EC061A8DC16100D536E9 /* UIBezierPath+Center.h in Headers */, + 66F2EC091A8DC16600D536E9 /* UIBezierPath+FirstLast.h in Headers */, + 66F2EC051A8DC16000D536E9 /* UIBezierPath+Clockwise.h in Headers */, + 66AB566C1B0D9830005E6FB1 /* UIBezierPath+Util.h in Headers */, + 66D1B7971AFEAC6F00210262 /* UIBezierPath+Trim.h in Headers */, + 9494C4A01F4715A000D5BCFD /* UIBezierPath+Trimming.h in Headers */, + 66F2EC031A8DC15B00D536E9 /* UIBezierPath+Description.h in Headers */, + 66F2EC021A8DC15A00D536E9 /* UIBezierPath+NSOSX.h in Headers */, + 66F2EC071A8DC16300D536E9 /* UIBezierPath+Performance_Private.h in Headers */, + 66F2EC081A8DC16500D536E9 /* UIBezierPath+NSOSX_Private.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 66B9D28B1A8D5FDE00CAC341 /* PerformanceBezierTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 66B9D2961A8D5FDF00CAC341 /* Build configuration list for PBXNativeTarget "PerformanceBezierTests" */; + buildPhases = ( + 66B9D2881A8D5FDE00CAC341 /* Sources */, + 66B9D2891A8D5FDE00CAC341 /* Frameworks */, + 66B9D28A1A8D5FDE00CAC341 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 6617530F1A8DC8300051D5CB /* PBXTargetDependency */, + ); + name = PerformanceBezierTests; + productName = PerformanceBezierTests; + productReference = 66B9D28C1A8D5FDE00CAC341 /* PerformanceBezierTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 66F2EBE21A8DC05100D536E9 /* PerformanceBezier */ = { + isa = PBXNativeTarget; + buildConfigurationList = 66F2EBF01A8DC05100D536E9 /* Build configuration list for PBXNativeTarget "PerformanceBezier" */; + buildPhases = ( + 66F2EBF31A8DC05E00D536E9 /* Prepare Build Script */, + 66F2EBDD1A8DC05100D536E9 /* Sources */, + 66F2EBDE1A8DC05100D536E9 /* Frameworks */, + 66F2EBDF1A8DC05100D536E9 /* Headers */, + 66F2EBE01A8DC05100D536E9 /* Resources */, + 66F2EBF41A8DC0AB00D536E9 /* Process Headers Script */, + 66F2EBF51A8DC0CD00D536E9 /* Build Framework Script */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PerformanceBezier; + productName = PerformanceBezier; + productReference = 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */; + productType = "com.apple.product-type.framework.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 661B30C91A7EB43A008549C7 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = MM; + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = "Milestone Made"; + TargetAttributes = { + 66B9D28B1A8D5FDE00CAC341 = { + CreatedOnToolsVersion = 6.1.1; + }; + 66F2EBE21A8DC05100D536E9 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 661B30CC1A7EB43A008549C7 /* Build configuration list for PBXProject "PerformanceBezier" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 661B30C81A7EB43A008549C7; + productRefGroup = 667617871A7EB50A00C0B447 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 66F2EBE21A8DC05100D536E9 /* PerformanceBezier */, + 66B9D28B1A8D5FDE00CAC341 /* PerformanceBezierTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 66B9D28A1A8D5FDE00CAC341 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66F2EBE01A8DC05100D536E9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 66F2EC0D1A8DC22000D536E9 /* PerformanceBezier-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 66F2EBF31A8DC05E00D536E9 /* Prepare Build Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Prepare Build Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nset +u\nif [[ $UFW_MASTER_SCRIPT_RUNNING ]]\nthen\n# Nothing for the slave script to do\nexit 0\nfi\nset -u\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nUFW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nUFW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$UFW_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nUFW_OTHER_PLATFORM=iphonesimulator\nelse\nUFW_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$UFW_SDK_PLATFORM$ ]]\nthen\nUFW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${UFW_OTHER_PLATFORM}\"\nelse\necho \"Could not find $UFW_SDK_PLATFORM in $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\nONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM:-$ONLY_ACTIVE_ARCH}\n\n# Short-circuit if all binaries are up to date\n\nif [[ -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && \\\n[[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n[[ ! \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -nt \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n([[ \"${ONLY_ACTIVE_PLATFORM}\" == \"YES\" ]] || \\\n([[ -f \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && \\\n[[ -f \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n[[ ! \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -nt \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]]\n)\n)\nthen\nexit 0\nfi\n\n\n# Clean other platform if needed\n\nif [[ ! -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && [[ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]]\nthen\necho \"Platform \\\"$UFW_SDK_PLATFORM\\\" was cleaned recently. Cleaning \\\"$UFW_OTHER_PLATFORM\\\" as well\"\necho xcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" clean\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" clean\nfi\n\n\n# Make sure we are building from fresh binaries\n\nrm -rf \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nrm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\n\nif [[ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]]\nthen\nrm -rf \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nrm -rf \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\nfi\n"; + }; + 66F2EBF41A8DC0AB00D536E9 /* Process Headers Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Process Headers Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\nHEADERS_ROOT=$SRCROOT/$PRODUCT_NAME\nFRAMEWORK_HEADERS_DIR=\"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME/Versions/$FRAMEWORK_VERSION/Headers\"\n\n## only header files expected at this point\nPUBLIC_HEADERS=$(find $FRAMEWORK_HEADERS_DIR/. -not -type d 2> /dev/null | sed -e \"s@.*/@@g\")\n\nFIND_OPTS=\"\"\nfor PUBLIC_HEADER in $PUBLIC_HEADERS; do\nif [ -n \"$FIND_OPTS\" ]; then\nFIND_OPTS=\"$FIND_OPTS -o\"\nfi\nFIND_OPTS=\"$FIND_OPTS -name '$PUBLIC_HEADER'\"\ndone\n\nif [ -n \"$FIND_OPTS\" ]; then\nfor ORIG_HEADER in $(eval \"find $HEADERS_ROOT/. $FIND_OPTS\" 2> /dev/null | sed -e \"s@^$HEADERS_ROOT/./@@g\"); do\nPUBLIC_HEADER=$(basename $ORIG_HEADER)\nRELATIVE_PATH=$(dirname $ORIG_HEADER)\nif [ -e $FRAMEWORK_HEADERS_DIR/$PUBLIC_HEADER ]; then\nmkdir -p \"$FRAMEWORK_HEADERS_DIR/$RELATIVE_PATH\"\nmv \"$FRAMEWORK_HEADERS_DIR/$PUBLIC_HEADER\" \"$FRAMEWORK_HEADERS_DIR/$RELATIVE_PATH/$PUBLIC_HEADER\"\nfi\ndone\nfi\n"; + }; + 66F2EBF51A8DC0CD00D536E9 /* Build Framework Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Build Framework Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\nset -e\n\nset +u\nif [[ $UFW_MASTER_SCRIPT_RUNNING ]]\nthen\n# Nothing for the slave script to do\nexit 0\nfi\nset -u\nexport UFW_MASTER_SCRIPT_RUNNING=1\n\n\n# Functions\n\n## List files in the specified directory, storing to the specified array.\n#\n# @param $1 The path to list\n# @param $2 The name of the array to fill\n#\n##\nlist_files ()\n{\n filelist=$(ls \"$1\")\n while read line\n do\n eval \"$2[\\${#$2[*]}]=\\\"\\$line\\\"\"\n done <<< \"$filelist\"\n}\n\n\n# Sanity check\n\nif [[ ! -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]]\nthen\necho \"Framework target \\\"${TARGET_NAME}\\\" had no source files to build from. Make sure your source files have the correct target membership\"\nexit 1\nfi\n\necho \"Target Executable: ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\n\n\n# Gather information\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nUFW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nUFW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$UFW_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nUFW_OTHER_PLATFORM=iphonesimulator\nelse\nUFW_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$UFW_SDK_PLATFORM$ ]]\nthen\nUFW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${UFW_OTHER_PLATFORM}\"\nelse\necho \"Could not find $UFW_SDK_PLATFORM in $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\nONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM:-$ONLY_ACTIVE_ARCH}\n\n# Short-circuit if all binaries are up to date.\n# We already checked the other platform in the prerun script.\n\nif [[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\" ]] && [[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${PRODUCT_NAME}\" ]] && [[ ! \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\" -nt \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${PRODUCT_NAME}\" ]]\nthen\nexit 0\nfi\n\nif [ \"${ONLY_ACTIVE_PLATFORM}\" == \"YES\" ]\nthen\necho \"ONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM}: Skipping other platform build\"\nelse\n# Make sure the other platform gets built\n\necho \"Build other platform\"\n\necho xcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" $ACTION\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" $ACTION\n\n\n# Build the fat static library binary\n\necho \"Create universal static library\"\n\necho \"$PLATFORM_DEVELOPER_BIN_DIR/libtool\" -static \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -o \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\"\n\"$PLATFORM_DEVELOPER_BIN_DIR/libtool\" -static \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -o \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\"\n\necho mv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}\"\nmv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}\"\nfi\n\n# Move executable to product name location\n\necho \"Moving Executable\"\necho \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\necho \"to\"\necho \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\nmv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\n# Build embedded framework structure\n\necho \"Build Embedded Framework\"\n\necho rm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\nrm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\necho mkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources\"\nmkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources\"\necho cp -a \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/\"\ncp -a \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/\"\n\ndeclare -a UFW_FILE_LIST\nlist_files \"${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" UFW_FILE_LIST\nfor filename in \"${UFW_FILE_LIST[@]}\"\ndo\nif [[ \"${filename}\" != \"Info.plist\" ]] && [[ ! \"${filename}\" =~ .*\\.lproj$ ]]\nthen\necho ln -sfh \"../${WRAPPER_NAME}/Resources/${filename}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources/${filename}\"\nln -sfh \"../${WRAPPER_NAME}/Resources/${filename}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources/${filename}\"\nfi\ndone\n\n\nif [ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]\nthen\n# Replace other platform's framework with a copy of this one (so that both have the same universal binary)\n\necho \"Copy from $UFW_SDK_PLATFORM to $UFW_OTHER_PLATFORM\"\n\necho rm -rf \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\nrm -rf \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\necho cp -a \"${BUILD_DIR}/${CONFIGURATION}-${UFW_SDK_PLATFORM}\" \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\ncp -a \"${BUILD_DIR}/${CONFIGURATION}-${UFW_SDK_PLATFORM}\" \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 66B9D2881A8D5FDE00CAC341 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 66B9D2A61A8D609200CAC341 /* PerformanceBezierAbstractTest.m in Sources */, + 66B9D2A71A8D609300CAC341 /* PerformanceBezierClockwiseTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66F2EBDD1A8DC05100D536E9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 66F2EBF61A8DC12600D536E9 /* UIBezierPath+Center.m in Sources */, + 66F2EBF71A8DC12800D536E9 /* UIBezierPath+Clockwise.m in Sources */, + 66F2EBFD1A8DC13D00D536E9 /* UIBezierPath+FirstLast.m in Sources */, + 66F2EBFC1A8DC13900D536E9 /* UIBezierPath+Uncached.m in Sources */, + 66F2EBFF1A8DC14100D536E9 /* JRSwizzle.m in Sources */, + 66F2EBFB1A8DC13700D536E9 /* UIBezierPath+Performance.m in Sources */, + 66F2EBFE1A8DC13F00D536E9 /* UIBezierPathProperties.m in Sources */, + 66AB566D1B0D9830005E6FB1 /* UIBezierPath+Util.m in Sources */, + 66F2EBF91A8DC13300D536E9 /* UIBezierPath+Description.m in Sources */, + 66F2EBFA1A8DC13500D536E9 /* UIBezierPath+NSOSX.m in Sources */, + 66F2EBF81A8DC13100D536E9 /* UIBezierPath+Equals.m in Sources */, + 9494C4A11F4715A000D5BCFD /* UIBezierPath+Trimming.m in Sources */, + 66D1B7981AFEAC6F00210262 /* UIBezierPath+Trim.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6617530F1A8DC8300051D5CB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 66F2EBE21A8DC05100D536E9 /* PerformanceBezier */; + targetProxy = 6617530E1A8DC8300051D5CB /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 661B30CD1A7EB43A008549C7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 661B30CE1A7EB43A008549C7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 66B9D2971A8D5FDF00CAC341 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(BUILT_PRODUCTS_DIR)/**", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = PerformanceBezierTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC++", + "-lstdc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 66B9D2981A8D5FDF00CAC341 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(BUILT_PRODUCTS_DIR)/**", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = PerformanceBezierTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC++", + "-lstdc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 66F2EBF11A8DC05100D536E9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = PerformanceBezier/Info.plist; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_PLATFORM = YES; + OTHER_LDFLAGS = "-ObjC++"; + PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = framework; + }; + name = Debug; + }; + 66F2EBF21A8DC05100D536E9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = PerformanceBezier/Info.plist; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_PLATFORM = YES; + OTHER_LDFLAGS = "-ObjC++"; + PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = framework; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 661B30CC1A7EB43A008549C7 /* Build configuration list for PBXProject "PerformanceBezier" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 661B30CD1A7EB43A008549C7 /* Debug */, + 661B30CE1A7EB43A008549C7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66B9D2961A8D5FDF00CAC341 /* Build configuration list for PBXNativeTarget "PerformanceBezierTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66B9D2971A8D5FDF00CAC341 /* Debug */, + 66B9D2981A8D5FDF00CAC341 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66F2EBF01A8DC05100D536E9 /* Build configuration list for PBXNativeTarget "PerformanceBezier" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66F2EBF11A8DC05100D536E9 /* Debug */, + 66F2EBF21A8DC05100D536E9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 661B30C91A7EB43A008549C7 /* Project object */; +} diff --git a/ios/PerformanceBezier/PerformanceBezier/Info.plist b/ios/PerformanceBezier/PerformanceBezier/Info.plist new file mode 100644 index 000000000..2c15fb5ce --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h b/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h new file mode 100644 index 000000000..7d29bc2ea --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h @@ -0,0 +1,13 @@ +// JRSwizzle.h semver:1.0 +// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/MIT +// https://github.com/rentzsch/jrswizzle + +#import + +@interface NSObject (JRSwizzle) + ++ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; ++ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m b/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m new file mode 100644 index 000000000..4e582bf5a --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m @@ -0,0 +1,134 @@ +// JRSwizzle.m semver:1.0 +// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/MIT +// https://github.com/rentzsch/jrswizzle + +#import "JRSwizzle.h" + +#if TARGET_OS_IPHONE + #import + #import +#else + #import +#endif + +#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \ + if (ERROR_VAR) { \ + NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \ + *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \ + code:-1 \ + userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \ + } +#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__) + +#if OBJC_API_VERSION >= 2 +#define GetClass(obj) object_getClass(obj) +#else +#define GetClass(obj) (obj ? obj->isa : Nil) +#endif + +@implementation NSObject (JRSwizzle) + ++ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ { +#if OBJC_API_VERSION >= 2 + Method origMethod = class_getInstanceMethod(self, origSel_); + if (!origMethod) { +#if TARGET_OS_IPHONE + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); +#else + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); +#endif + return NO; + } + + Method altMethod = class_getInstanceMethod(self, altSel_); + if (!altMethod) { +#if TARGET_OS_IPHONE + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); +#else + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); +#endif + return NO; + } + + class_addMethod(self, + origSel_, + class_getMethodImplementation(self, origSel_), + method_getTypeEncoding(origMethod)); + class_addMethod(self, + altSel_, + class_getMethodImplementation(self, altSel_), + method_getTypeEncoding(altMethod)); + + method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_)); + return YES; +#else + // Scan for non-inherited methods. + Method directOriginalMethod = NULL, directAlternateMethod = NULL; + + void *iterator = NULL; + struct objc_method_list *mlist = class_nextMethodList(self, &iterator); + while (mlist) { + int method_index = 0; + for (; method_index < mlist->method_count; method_index++) { + if (mlist->method_list[method_index].method_name == origSel_) { + assert(!directOriginalMethod); + directOriginalMethod = &mlist->method_list[method_index]; + } + if (mlist->method_list[method_index].method_name == altSel_) { + assert(!directAlternateMethod); + directAlternateMethod = &mlist->method_list[method_index]; + } + } + mlist = class_nextMethodList(self, &iterator); + } + + // If either method is inherited, copy it up to the target class to make it non-inherited. + if (!directOriginalMethod || !directAlternateMethod) { + Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL; + if (!directOriginalMethod) { + inheritedOriginalMethod = class_getInstanceMethod(self, origSel_); + if (!inheritedOriginalMethod) { + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); + return NO; + } + } + if (!directAlternateMethod) { + inheritedAlternateMethod = class_getInstanceMethod(self, altSel_); + if (!inheritedAlternateMethod) { + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); + return NO; + } + } + + int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1; + struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1))); + hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind + hoisted_method_list->method_count = hoisted_method_count; + Method hoisted_method = hoisted_method_list->method_list; + + if (!directOriginalMethod) { + bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method)); + directOriginalMethod = hoisted_method++; + } + if (!directAlternateMethod) { + bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method)); + directAlternateMethod = hoisted_method; + } + class_addMethods(self, hoisted_method_list); + } + + // Swizzle. + IMP temp = directOriginalMethod->method_imp; + directOriginalMethod->method_imp = directAlternateMethod->method_imp; + directAlternateMethod->method_imp = temp; + + return YES; +#endif +} + ++ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ { + return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_]; +} + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist b/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist new file mode 100644 index 000000000..c03188bc4 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist @@ -0,0 +1,5 @@ + + + +CFBundleDevelopmentRegion + diff --git a/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h b/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h new file mode 100644 index 000000000..aa2986e46 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h @@ -0,0 +1,20 @@ +// +// PerformanceBezier.h +// PerformanceBezier +// +// Created by Adam Wulf on 2/1/15. +// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. +// + +#define CGPointNotFound CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX) + +#import +#import "UIBezierPathProperties.h" +#import "UIBezierPath+Clockwise.h" +#import "UIBezierPath+Performance.h" +#import "UIBezierPath+NSOSX.h" +#import "UIBezierPath+Equals.h" +#import "UIBezierPath+Center.h" +#import "UIBezierPath+Trim.h" +#import "UIBezierPath+Util.h" +#import "UIBezierPath+Trimming.h" diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h new file mode 100644 index 000000000..fc40810a2 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h @@ -0,0 +1,17 @@ +// +// UIBezierPath+Center.h +// ios-hand-shadows +// +// Created by Adam Wulf on 2/2/15. +// Copyright (c) 2015 Milestone Made. All rights reserved. +// + +#import + +@interface UIBezierPath (Center) + +// returns a point in the center of +// the path's bounds +-(CGPoint) center; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m new file mode 100644 index 000000000..6273ccd18 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m @@ -0,0 +1,18 @@ +// +// UIBezierPath+Center.m +// ios-hand-shadows +// +// Created by Adam Wulf on 2/2/15. +// Copyright (c) 2015 Milestone Made. All rights reserved. +// + +#import "UIBezierPath+Center.h" + +@implementation UIBezierPath (Center) + + +-(CGPoint) center{ + return CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); +} + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h new file mode 100644 index 000000000..146377de6 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h @@ -0,0 +1,18 @@ +// +// UIBezierPath+Clockwise.h +// PerformanceBezier +// +// Created by Adam Wulf on 1/7/14. +// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. +// + +#import + +@interface UIBezierPath (Clockwise) + +// +// returns YES if the path elements curve +// around in clockwise direction +-(BOOL) isClockwise; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m new file mode 100644 index 000000000..18e34c84a --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m @@ -0,0 +1,50 @@ +// +// UIBezierPath+Clockwise.m +// PerformanceBezier +// +// Created by Adam Wulf on 1/7/14. +// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. +// + +#import "UIBezierPath+Clockwise.h" +#import "PerformanceBezier.h" + +@implementation UIBezierPath (Clockwise) + +-(BOOL) isClockwise{ + + __block CGPoint lastMoveTo = CGPointZero; + __block CGPoint lastPoint = CGPointZero; + __block CGFloat sum = 0; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx){ + if(element.type == kCGPathElementMoveToPoint){ + lastMoveTo = element.points[0]; + lastPoint = lastMoveTo; + }else if(element.type == kCGPathElementAddLineToPoint){ + sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint]; + lastPoint = element.points[0]; + }else if(element.type == kCGPathElementAddQuadCurveToPoint){ + sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint]; + sum += [self calculateAreaFor:element.points[1] andPoint:element.points[0]]; + lastPoint = element.points[1]; + }else if(element.type == kCGPathElementAddCurveToPoint){ + sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint]; + sum += [self calculateAreaFor:element.points[1] andPoint:element.points[0]]; + sum += [self calculateAreaFor:element.points[2] andPoint:element.points[1]]; + lastPoint = element.points[2]; + }else if(element.type == kCGPathElementCloseSubpath){ + sum += [self calculateAreaFor:lastMoveTo andPoint:lastPoint]; + lastPoint = element.points[0]; + lastPoint = lastMoveTo; + } + }]; + return sum >= 0; +} + + +- (CGFloat) calculateAreaFor:(CGPoint)point1 andPoint:(CGPoint)point2{ + return (point2.x - point1.x) * (point2.y + point1.y); +} + + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h new file mode 100644 index 000000000..b125ce4d4 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h @@ -0,0 +1,13 @@ +// +// UIBezierPath+Description.h +// LooseLeaf +// +// Created by Adam Wulf on 12/17/13. +// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. +// + +#import + +@interface UIBezierPath (Description) + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m new file mode 100644 index 000000000..371c61f40 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m @@ -0,0 +1,57 @@ +// +// UIBezierPath+Description.m +// LooseLeaf +// +// Created by Adam Wulf on 12/17/13. +// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. +// + +#import "UIBezierPath+Description.h" +#import "UIBezierPath+NSOSX.h" +#import "JRSwizzle.h" + +@implementation UIBezierPath (Description) + + +// +// create a human readable objective-c string for +// the path. this lets a dev easily print out the bezier +// from the debugger, and copy the result directly back into +// code. Perfect for printing out runtime generated beziers +// for use later in tests. +-(NSString*) swizzle_description{ + __block NSString* str = @"path = [UIBezierPath bezierPath];\n"; + [self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){ + if(ele.type == kCGPathElementAddCurveToPoint){ + CGPoint curveTo = ele.points[2]; + CGPoint ctrl1 = ele.points[0]; + CGPoint ctrl2 = ele.points[1]; + str = [str stringByAppendingFormat:@"[path addCurveToPoint:CGPointMake(%f, %f) controlPoint1:CGPointMake(%f, %f) controlPoint2:CGPointMake(%f, %f)];\n", curveTo.x, curveTo.y, ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y]; + }else if(ele.type == kCGPathElementAddLineToPoint){ + CGPoint lineTo = ele.points[0]; + str = [str stringByAppendingFormat:@"[path addLineToPoint:CGPointMake(%f, %f)];\n", lineTo.x, lineTo.y]; + }else if(ele.type == kCGPathElementAddQuadCurveToPoint){ + CGPoint curveTo = ele.points[2]; + CGPoint ctrl = ele.points[0]; + str = [str stringByAppendingFormat:@"[path addQuadCurveToPoint:CGPointMake(%f, %f) controlPoint:CGPointMake(%f, %f)];\n", curveTo.x, curveTo.y, ctrl.x, ctrl.y]; + }else if(ele.type == kCGPathElementCloseSubpath){ + [self closePath]; + str = [str stringByAppendingString:@"[path closePath];\n"]; + }else if(ele.type == kCGPathElementMoveToPoint){ + CGPoint moveTo = ele.points[0]; + str = [str stringByAppendingFormat:@"[path moveToPoint:CGPointMake(%f, %f)];\n", moveTo.x, moveTo.y]; + } + }]; + return str; +} + + ++(void)load{ + @autoreleasepool { + NSError *error = nil; + [UIBezierPath jr_swizzleMethod:@selector(description) + withMethod:@selector(swizzle_description) + error:&error]; + } +} +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h new file mode 100644 index 000000000..7b052d981 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h @@ -0,0 +1,18 @@ +// +// UIBezierPath+Equals.h +// LooseLeaf +// +// Created by Adam Wulf on 6/3/14. +// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. +// + +#import + +@interface UIBezierPath (Equals) + +// returns YES if the input path is equal +// to the current path. convenience wrapper +// around CGPathEqualToPath +-(BOOL) isEqualToBezierPath:(UIBezierPath*)path; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m new file mode 100644 index 000000000..8649e471b --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m @@ -0,0 +1,18 @@ +// +// UIBezierPath+Debug.m +// LooseLeaf +// +// Created by Adam Wulf on 6/3/14. +// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. +// + +#import "UIBezierPath+Equals.h" +#import "PerformanceBezier.h" + +@implementation UIBezierPath (Equals) + +-(BOOL) isEqualToBezierPath:(UIBezierPath*)path{ + return CGPathEqualToPath(self.CGPath, path.CGPath); +} + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h new file mode 100644 index 000000000..14e2bc8fb --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h @@ -0,0 +1,21 @@ +// +// UIBezierPath+FirstLast.h +// iOS-UIBezierPath-Performance +// +// Created by Adam Wulf on 2/1/15. +// +// + +#import + +@interface UIBezierPath (FirstLast) + +// calculates the first point of the path, +// useful if its not already cached +-(CGPoint) lastPointCalculated; + +// calculates the last point of the path, +// useful if its not already cached +-(CGPoint) firstPointCalculated; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m new file mode 100644 index 000000000..89c98cea9 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m @@ -0,0 +1,61 @@ +// +// UIBezierPath+FirstLast.m +// iOS-UIBezierPath-Performance +// +// Created by Adam Wulf on 2/1/15. +// +// + +#import "UIBezierPath+FirstLast.h" +#import "UIBezierPath+NSOSX.h" + +@implementation UIBezierPath (FirstLast) + +-(CGPoint) lastPointCalculated{ + __block CGPoint firstPoint = CGPointZero; + __block CGPoint lastPoint = CGPointZero; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { + CGPoint currPoint = CGPointZero; + if(element.type == kCGPathElementMoveToPoint){ + currPoint = element.points[0]; + firstPoint = currPoint; + }else if(element.type == kCGPathElementAddLineToPoint){ + currPoint = element.points[0]; + }else if(element.type == kCGPathElementCloseSubpath){ + currPoint = firstPoint; + }else if(element.type == kCGPathElementAddCurveToPoint){ + currPoint = element.points[2]; + }else if(element.type == kCGPathElementAddQuadCurveToPoint){ + currPoint = element.points[1]; + } + if(idx == 0){ + // path should've begun with a moveTo, + // but this is a sanity check for malformed + // paths + firstPoint = currPoint; + } + lastPoint = currPoint; + }]; + return lastPoint; +} + +-(CGPoint) firstPointCalculated{ + __block CGPoint firstPoint = CGPointZero; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { + if(idx == 0){ + if(element.type == kCGPathElementMoveToPoint || + element.type == kCGPathElementAddLineToPoint){ + firstPoint = element.points[0]; + }else if(element.type == kCGPathElementCloseSubpath){ + firstPoint = firstPoint; + }else if(element.type == kCGPathElementAddCurveToPoint){ + firstPoint = element.points[2]; + }else if(element.type == kCGPathElementAddQuadCurveToPoint){ + firstPoint = element.points[1]; + } + } + }]; + return firstPoint; +} + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h new file mode 100644 index 000000000..53732baa3 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h @@ -0,0 +1,54 @@ +// +// UIBezierPath+NSOSX.h +// PaintingSample +// +// Created by Adam Wulf on 10/5/12. +// +// + +#import + +@interface UIBezierPath (NSOSX) + +// A flattened version of the path object. +@property(nonatomic,readonly) UIBezierPath* bezierPathByFlatteningPath; +// returns the number of elements in this path +@property(nonatomic,readonly) NSInteger elementCount; +// YES if the path is made without curves, NO otherwise +@property(nonatomic,assign) BOOL isFlat; + +-(UIBezierPath*) bezierPathByFlatteningPathAndImmutable:(BOOL)returnCopy; + +// returns the element at the given index, and also +// fills the points[] array with the element's points. +// the points property of the CGPathElement is owned by +// the internal cache, so if you need the points, you should +// retrieve them through the points parameter. +- (CGPathElement)elementAtIndex:(NSInteger)index associatedPoints:(CGPoint[])points; + +// returns the element at the given index. If you also need +// access to the element's points, then use the method above. +- (CGPathElement)elementAtIndex:(NSInteger)index; + +// modifies the element at the index to have the input +// points associated with it. This allows modifying the +// path in place +- (void)setAssociatedPoints:(CGPoint[])points atIndex:(NSInteger)index; + +// returns the bounds of the path including its control points +-(CGRect) controlPointBounds; + +// iterate over each element in the path with the input block +-(void) iteratePathWithBlock:(void (^)(CGPathElement element,NSUInteger idx))block; + +// helper method to return the number of points for any input element +// based on its type. ie, an element of type +// kCGPathElementAddCurveToPoint returns 3 ++(NSInteger) numberOfPointsForElement:(CGPathElement)element; + +// helper method to copy a path element to a new element. +// Note: you are responsible for calling free(yourElement.points) +// when you are done with its return value. ++(CGPathElement*) copyCGPathElement:(CGPathElement*)element; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m new file mode 100644 index 000000000..320dffb72 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m @@ -0,0 +1,522 @@ +// +// UIBezierPath+NSOSX.m +// PaintingSample +// +// Created by Adam Wulf on 10/5/12. +// +// + +#import "UIBezierPath+NSOSX.h" +#import +#import "JRSwizzle.h" +#import "UIBezierPath+NSOSX_Private.h" +#import "UIBezierPath+Performance_Private.h" +#import "UIBezierPath+Performance.h" +#import "UIBezierPath+Uncached.h" +#import "UIBezierPath+Util.h" + + +static char ELEMENT_ARRAY; +static CGFloat idealFlatness = .01; + +@implementation UIBezierPath (NSOSX) + + +#pragma mark - Properties + +/** + * this is a property on the category, as described in: + * https://github.com/techpaa/iProperties + * + * + * this array is for private PerformanceBezier use only + * + * Since iOS doesn't allow for index lookup of CGPath elements (only option is CGPathApply) + * this array will cache the elements after they've been looked up once + */ +-(void) freeCurrentElementCacheArray{ + NSMutableArray* currentArray = objc_getAssociatedObject(self, &ELEMENT_ARRAY); + if([currentArray count]){ + while([currentArray count]){ + NSValue* val = [currentArray lastObject]; + CGPathElement* element = [val pointerValue]; + free(element->points); + free(element); + [currentArray removeLastObject]; + } + } + objc_setAssociatedObject(self, &ELEMENT_ARRAY, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +-(void)setElementCacheArray:(NSMutableArray *)_elementCacheArray{ + [self freeCurrentElementCacheArray]; + NSMutableArray* newArray = [NSMutableArray array]; + for(NSValue* val in _elementCacheArray){ + CGPathElement* element = [val pointerValue]; + CGPathElement* copiedElement = [UIBezierPath copyCGPathElement:element]; + [newArray addObject:[NSValue valueWithPointer:copiedElement]]; + } + objc_setAssociatedObject(self, &ELEMENT_ARRAY, newArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +-(NSMutableArray*)elementCacheArray{ + NSMutableArray* ret = objc_getAssociatedObject(self, &ELEMENT_ARRAY); + if(!ret){ + ret = [NSMutableArray array]; + self.elementCacheArray = ret; + } + return ret; +} + + + +#pragma mark - UIBezierPath + +/** + * returns the CGPathElement at the specified index, optionally + * also returning the elements points in the 2nd parameter + * + * this method is meant to mimic UIBezierPath's method of the same name + */ +- (CGPathElement)elementAtIndex:(NSInteger)askingForIndex associatedPoints:(CGPoint[])points{ + __block BOOL didReturn = NO; + __block CGPathElement returnVal; + if(askingForIndex < [self.elementCacheArray count]){ + returnVal = *(CGPathElement*)[[self.elementCacheArray objectAtIndex:askingForIndex] pointerValue]; +#ifdef MMPreventBezierPerformance + [self simulateNoBezierCaching]; +#endif + }else{ + __block UIBezierPath* this = self; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ + int numberInCache = (int) [this.elementCacheArray count]; + if(!didReturn || currentIndex == [this.elementCacheArray count]){ + if(currentIndex == numberInCache){ + [this.elementCacheArray addObject:[NSValue valueWithPointer:[UIBezierPath copyCGPathElement:&element]]]; + } + if(currentIndex == askingForIndex){ + returnVal = *(CGPathElement*)[[this.elementCacheArray objectAtIndex:askingForIndex] pointerValue]; + didReturn = YES; + } + } + }]; + } + + if(points){ + for(int i=0;i<[UIBezierPath numberOfPointsForElement:returnVal];i++){ + points[i] = returnVal.points[i]; + } + } + return returnVal; +} + + +/** + * returns the CGPathElement at the specified index + * + * this method is meant to mimic UIBezierPath's method of the same name + */ +- (CGPathElement)elementAtIndex:(NSInteger)index{ + return [self elementAtIndex:index associatedPoints:NULL]; +} + + +/** + * updates the point in the path with the new input points + * + * TODO: this method is entirely untested + */ +- (void)setAssociatedPoints:(CGPoint[])points atIndex:(NSInteger)index{ + NSMutableDictionary* params = [NSMutableDictionary dictionary]; + [params setObject:[NSNumber numberWithInteger:index] forKey:@"index"]; + [params setObject:[NSValue valueWithPointer:points] forKey:@"points"]; + CGPathApply(self.CGPath, params, updatePathElementAtIndex); + +} +// +// helper function for the setAssociatedPoints: method +void updatePathElementAtIndex(void* info, const CGPathElement* element) { + NSMutableDictionary* params = (NSMutableDictionary*)info; + int currentIndex = 0; + if([params objectForKey:@"curr"]){ + currentIndex = [[params objectForKey:@"curr"] intValue] + 1; + } + if(currentIndex == [[params objectForKey:@"index"] intValue]){ + CGPoint* points = [[params objectForKey:@"points"] pointerValue]; + for(int i=0;i<[UIBezierPath numberOfPointsForElement:*element];i++){ + element->points[i] = points[i]; + } + CGPathElement* returnVal = [UIBezierPath copyCGPathElement:(CGPathElement*)element]; + [params setObject:[NSValue valueWithPointer:returnVal] forKey:@"element"]; + } + [params setObject:[NSNumber numberWithInt:currentIndex] forKey:@"curr"]; +} + +/** + * Returns the bounding box containing all points in a graphics path. + * The bounding box is the smallest rectangle completely enclosing + * all points in the path, including control points for Bézier and + * quadratic curves. + * + * this method is meant to mimic UIBezierPath's method of the same name + */ +-(CGRect) controlPointBounds{ + return CGPathGetBoundingBox(self.CGPath); +} + + +- (NSInteger)elementCount{ + UIBezierPathProperties* props = [self pathProperties]; + if(props.cachedElementCount){ +#ifdef MMPreventBezierPerformance + [self simulateNoBezierCaching]; +#endif + return props.cachedElementCount; + } + NSMutableDictionary* params = [NSMutableDictionary dictionary]; + [params setObject:[NSNumber numberWithInteger:0] forKey:@"count"]; + [params setObject:self forKey:@"self"]; + [self retain]; + CGPathApply(self.CGPath, params, countPathElement); + [self release]; + NSInteger ret = [[params objectForKey:@"count"] integerValue]; + props.cachedElementCount = ret; + return ret; +} +// helper function +void countPathElement(void* info, const CGPathElement* element) { + NSMutableDictionary* params = (NSMutableDictionary*) info; + UIBezierPath* this = [params objectForKey:@"self"]; + NSInteger count = [[params objectForKey:@"count"] integerValue]; + [params setObject:[NSNumber numberWithInteger:(count + 1)] forKey:@"count"]; + if(count == [this.elementCacheArray count]){ + [this.elementCacheArray addObject:[NSValue valueWithPointer:[UIBezierPath copyCGPathElement:(CGPathElement*)element]]]; + } +} + +-(void) iteratePathWithBlock:(void (^)(CGPathElement element,NSUInteger idx))block{ + void (^copiedBlock)(CGPathElement element) = [block copy]; + NSMutableDictionary* params = [NSMutableDictionary dictionary]; + [params setObject:copiedBlock forKey:@"block"]; + CGPathApply(self.CGPath, params, blockWithElement); + [copiedBlock release]; +} + +// helper function +static void blockWithElement(void* info, const CGPathElement* element) { + NSMutableDictionary* params = (NSMutableDictionary*) info; + void (^block)(CGPathElement element,NSUInteger idx) = [params objectForKey:@"block"]; + NSUInteger index = [[params objectForKey:@"index"] unsignedIntegerValue]; + block(*element, index); + [params setObject:@(index+1) forKey:@"index"]; +} + +#pragma mark - Flat + + + +#pragma mark - Properties + + +/** + * this is a property on the category, as described in: + * https://github.com/techpaa/iProperties + */ +-(void)setIsFlat:(BOOL)isFlat{ + [self pathProperties].isFlat = isFlat; +} + +/** + * return YES if this bezier path is made up of only + * moveTo, closePath, and lineTo elements + * + * TODO + * this method helps caching flattened paths internally + * to this category, but is not yet fit for public use. + * + * detecting when this path is flat would mean we'd have + * to also swizzle the constructors to bezier paths + */ +-(BOOL) isFlat{ + return [self pathProperties].isFlat; +} + +#pragma mark - UIBezierPath + + +/** + * call this method on a UIBezierPath to generate + * a new flattened path + * + * This category is named after Athar Luqman Ahmad, who + * wrote a masters thesis about minimizing the number of + * lines required to flatten a bezier curve + * + * The thesis is available here: + * http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf + * + * The algorithm that I use as of 10/09/2012 is a simple + * recursive algorithm that doesn't use any of ahmed's + * optimizations yet + * + * TODO: add in Ahmed's optimizations + */ +-(UIBezierPath*) bezierPathByFlatteningPath{ + return [self bezierPathByFlatteningPathAndImmutable:NO]; +} +/** + * @param shouldBeImmutable: YES if this function should return a distinct UIBezier, NO otherwise + * + * if the caller plans to modify the returned path, then shouldBeImmutable should + * be called with NO. + * + * if the caller only plans to iterate over and look at the returned value, + * then shouldBeImmutable should be YES - this is considerably faster to not + * return a copy if the value will be treated as immutable + */ +-(UIBezierPath*) bezierPathByFlatteningPathAndImmutable:(BOOL)willBeImmutable{ + UIBezierPathProperties* props = [self pathProperties]; + UIBezierPath* ret = props.bezierPathByFlatteningPath; + if(ret){ + if(willBeImmutable) return ret; + return [[ret copy] autorelease]; + } + if(self.isFlat){ + if(willBeImmutable) return self; + return [[self copy] autorelease]; + } + + __block NSInteger flattenedElementCount = 0; + UIBezierPath *newPath = [UIBezierPath bezierPath]; + NSInteger elements = [self elementCount]; + NSInteger n; + CGPoint pointForClose = CGPointMake (0.0, 0.0); + CGPoint lastPoint = CGPointMake (0.0, 0.0); + + for (n = 0; n < elements; ++n) + { + CGPoint points[3]; + CGPathElement element = [self elementAtIndex:n associatedPoints:points]; + + switch (element.type) + { + case kCGPathElementMoveToPoint: + [newPath moveToPoint:points[0]]; + pointForClose = lastPoint = points[0]; + flattenedElementCount++; + continue; + + case kCGPathElementAddLineToPoint: + [newPath addLineToPoint:points[0]]; + lastPoint = points[0]; + flattenedElementCount++; + break; + + case kCGPathElementAddQuadCurveToPoint: + case kCGPathElementAddCurveToPoint: + { + + // + // handle both curve types gracefully + CGPoint curveTo; + CGPoint ctrl1; + CGPoint ctrl2; + if(element.type == kCGPathElementAddQuadCurveToPoint){ + curveTo = element.points[1]; + ctrl1 = element.points[0]; + ctrl2 = ctrl1; + }else if(element.type == kCGPathElementAddCurveToPoint){ + curveTo = element.points[2]; + ctrl1 = element.points[0]; + ctrl2 = element.points[1]; + } + + // + // ok, this is the bezier for our current element + CGPoint bezier[4] = { lastPoint, ctrl1, ctrl2, curveTo }; + + + // + // define our recursive function that will + // help us split the curve up as needed + void (^__block flattenCurve)(UIBezierPath* newPath, CGPoint startPoint, CGPoint bez[4]) = ^(UIBezierPath* newPath, CGPoint startPoint, CGPoint bez[4]){ + // + // first, calculate the error rate for + // a line segement between the start/end points + // vs the curve + + CGPoint onCurve = bezierPointAtT(bez, .5); + + CGFloat error = distanceOfPointToLine(onCurve, startPoint, bez[2]); + + + // + // if that error is less than our accepted + // level of error, then just add a line, + // + // otherwise, split the curve in half and recur + if (error <= idealFlatness) + { + [newPath addLineToPoint:bez[3]]; + flattenedElementCount++; + } + else + { + CGPoint bez1[4], bez2[4]; + subdivideBezierAtT(bez, bez1, bez2, .5); + // now we've split the curve in half, and have + // two bezier curves bez1 and bez2. recur + // on these two halves + flattenCurve(newPath, startPoint, bez1); + flattenCurve(newPath, startPoint, bez2); + } + }; + + flattenCurve(newPath, lastPoint, bezier); + + lastPoint = points[2]; + break; + } + + case kCGPathElementCloseSubpath: + [newPath closePath]; + lastPoint = pointForClose; + flattenedElementCount++; + break; + + default: + break; + } + } + + // since we just built the flattened path + // we know how many elements there are, so cache that + UIBezierPathProperties* newPathProps = [newPath pathProperties]; + newPathProps.cachedElementCount = flattenedElementCount; + + props.bezierPathByFlatteningPath = newPath; + + return [self bezierPathByFlatteningPathAndImmutable:willBeImmutable]; +} + + +#pragma mark - Helper + +/** + * returns the length of the points array for the input + * CGPathElement element + */ ++(NSInteger) numberOfPointsForElement:(CGPathElement)element{ + NSInteger nPoints = 0; + switch (element.type) + { + case kCGPathElementMoveToPoint: + nPoints = 1; + break; + case kCGPathElementAddLineToPoint: + nPoints = 1; + break; + case kCGPathElementAddQuadCurveToPoint: + nPoints = 2; + break; + case kCGPathElementAddCurveToPoint: + nPoints = 3; + break; + case kCGPathElementCloseSubpath: + nPoints = 0; + break; + default: + nPoints = 0; + } + return nPoints; +} + + +/** + * copies the input CGPathElement + * + * TODO: I currently never free the memory assigned for the points array + * https://github.com/adamwulf/DrawKit-iOS/issues/4 + */ ++(CGPathElement*) copyCGPathElement:(CGPathElement*)element{ + CGPathElement* ret = malloc(sizeof(CGPathElement)); + if(!ret){ + @throw [NSException exceptionWithName:@"Memory Exception" reason:@"can't malloc" userInfo:nil]; + } + NSInteger numberOfPoints = [UIBezierPath numberOfPointsForElement:*element]; + if(numberOfPoints){ + ret->points = malloc(sizeof(CGPoint) * numberOfPoints); + }else{ + ret->points = NULL; + } + ret->type = element->type; + + for(int i=0;ipoints[i] = element->points[i]; + } + return ret; +} + + + + +#pragma mark - Swizzling + +/////////////////////////////////////////////////////////////////////////// +// +// All of these methods are to listen to UIBezierPath method calls +// so that we can add new functionality on top of them without +// changing any of the default behavior. +// +// These methods help maintain: +// 1. cachedElementCount +// 2. elementCacheArray +// 3. keeping cache's valid across copying + + +-(void) nsosx_swizzle_removeAllPoints{ + [self setElementCacheArray:nil]; + [self nsosx_swizzle_removeAllPoints]; +} + +-(UIBezierPath*) nsosx_swizzle_copy{ + UIBezierPath* ret = [self nsosx_swizzle_copy]; + // note, when setting the array here, it will actually be making + // a mutable copy of the input array, so the copied + // path will have its own version. + [ret setElementCacheArray:self.elementCacheArray]; + return ret; +} +-(void) nsosx_swizzle_applyTransform:(CGAffineTransform)transform{ + [self setElementCacheArray:nil]; + [self pathProperties].hasLastPoint = NO; + [self pathProperties].hasFirstPoint = NO; + [self nsosx_swizzle_applyTransform:transform]; +} + + + +-(void) nsosx_swizzle_dealloc{ + [self freeCurrentElementCacheArray]; + [self nsosx_swizzle_dealloc]; +} + ++(void)load{ + @autoreleasepool { + NSError *error = nil; + [UIBezierPath jr_swizzleMethod:@selector(removeAllPoints) + withMethod:@selector(nsosx_swizzle_removeAllPoints) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(applyTransform:) + withMethod:@selector(nsosx_swizzle_applyTransform:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(copy) + withMethod:@selector(nsosx_swizzle_copy) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(dealloc) + withMethod:@selector(nsosx_swizzle_dealloc) + error:&error]; + } +} + + + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h new file mode 100644 index 000000000..91dd1c789 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h @@ -0,0 +1,29 @@ +// +// UIBezierPath+NSOSX_Private.h +// PerformanceBezier +// +// Created by Adam Wulf on 10/9/12. +// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. +// + +#ifndef PerformanceBezier_UIBezierPath_NSOSX_Private_h +#define PerformanceBezier_UIBezierPath_NSOSX_Private_h + + +@interface UIBezierPath (NSOSX_Private) + +// cache of path elements +@property(nonatomic,retain) NSMutableArray* elementCacheArray; + +// cache of element count +@property(nonatomic,assign) NSInteger cachedElementCount; + +// helper functions to prime the above caches +void countPathElement(void* info, const CGPathElement* element); + +void updatePathElementAtIndex(void* info, const CGPathElement* element); + +@end + + +#endif diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h new file mode 100644 index 000000000..58f56c936 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h @@ -0,0 +1,49 @@ +// +// UIBezierPath+Performance.h +// PerformanceBezier +// +// Created by Adam Wulf on 1/31/15. +// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. +// + +#import +#import "UIBezierPathProperties.h" + +CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t); + +@interface UIBezierPath (Performance) + +-(UIBezierPathProperties*) pathProperties; + +// returns the last point of the bezier path. +// if the path ends with a kCGPathElementClosed, +// then the first point of that subpath is returned +-(CGPoint) lastPoint; + +// returns the first point of the bezier path +-(CGPoint) firstPoint; + +// returns the tangent at the very end of the path +// in radians +-(CGFloat) tangentAtEnd; + +// returns YES if the path is closed (or contains at least 1 closed subpath) +// returns NO otherwise +-(BOOL) isClosed; + +// returns the tangent of the bezier path at the given t value +- (CGPoint) tangentOnPathAtElement:(NSInteger)elementIndex andTValue:(CGFloat)tVal; + +// for the input bezier curve [start, ctrl1, ctrl2, end] +// return the point at the input T value ++(CGPoint) pointAtT:(CGFloat)t forBezier:(CGPoint*)bez; + +// for the input bezier curve [start, ctrl1, ctrl2, end] +// return the tangent at the input T value ++(CGPoint) tangentAtT:(CGFloat)t forBezier:(CGPoint*)bez; + +// fill the input point array with [start, ctrl1, ctrl2, end] +// for the element at the given index +-(void) fillBezier:(CGPoint[4])bezier forElement:(NSInteger)elementIndex; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m new file mode 100644 index 000000000..11cf809a5 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m @@ -0,0 +1,525 @@ +// +// UIBezierPath+Performance.m +// PerformanceBezier +// +// Created by Adam Wulf on 1/31/15. +// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. +// + +#import "UIBezierPath+Performance.h" +#import "UIBezierPath+Performance_Private.h" +#import "UIBezierPath+FirstLast.h" +#import "UIBezierPath+NSOSX.h" +#import "UIBezierPath+Uncached.h" +#import +#import "JRSwizzle.h" + +static char BEZIER_PROPERTIES; + +@implementation UIBezierPath (Performance) + +-(UIBezierPathProperties*) pathProperties{ + UIBezierPathProperties* props = objc_getAssociatedObject(self, &BEZIER_PROPERTIES); + if(!props){ + props = [[[UIBezierPathProperties alloc] init] autorelease]; + objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return props; +} + +-(void)setTangentAtEnd:(CGFloat)tangent{ + [self pathProperties].tangentAtEnd = tangent; +} + +-(CGPoint)lastPoint{ + UIBezierPathProperties* props = [self pathProperties]; + if(!props.hasLastPoint){ + props.hasLastPoint = YES; + props.lastPoint = [self lastPointCalculated]; +#ifdef MMPreventBezierPerformance + }else{ + [self simulateNoBezierCaching]; +#endif + } + return props.lastPoint; +} +-(CGPoint)firstPoint{ + UIBezierPathProperties* props = [self pathProperties]; + if(!props.hasFirstPoint){ + props.hasFirstPoint = YES; + props.firstPoint = [self firstPointCalculated]; +#ifdef MMPreventBezierPerformance + }else{ + [self simulateNoBezierCaching]; +#endif + } + return props.firstPoint; +} +-(BOOL) isClosed{ + UIBezierPathProperties* props = [self pathProperties]; + if(!props.knowsIfClosed){ + // we dont know if the path is closed, so + // find a close element if we have one + [self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){ + if(ele.type == kCGPathElementCloseSubpath){ + props.isClosed = YES; + } + }]; + props.knowsIfClosed = YES; +#ifdef MMPreventBezierPerformance + }else{ + [self simulateNoBezierCaching]; +#endif + } + return props.isClosed; +} +-(CGFloat) tangentAtEnd{ +#ifdef MMPreventBezierPerformance + [self simulateNoBezierCaching]; +#endif + return [self pathProperties].tangentAtEnd; +} + +/** + * this is a property on the category, as described in: + * https://github.com/techpaa/iProperties + */ +-(void)setBezierPathByFlatteningPath:(UIBezierPath *)bezierPathByFlatteningPath{ + [self pathProperties].bezierPathByFlatteningPath = bezierPathByFlatteningPath; +} + + + +/** + * this is a property on the category, as described in: + * https://github.com/techpaa/iProperties + * + * + * this is for internal PerformanceBezier use only + * + * Since iOS doesn't allow a quick lookup for element count, + * this property will act as a cache for the element count after + * it has been calculated once + */ +-(void)setCachedElementCount:(NSInteger)_cachedElementCount{ + [self pathProperties].cachedElementCount = _cachedElementCount; +} + +-(NSInteger)cachedElementCount{ + return [self pathProperties].cachedElementCount; +} + + + + +-(void) fillBezier:(CGPoint[4])bezier forElement:(NSInteger)elementIndex{ + if(elementIndex >= [self elementCount] || elementIndex < 0){ + @throw [NSException exceptionWithName:@"BezierElementException" reason:@"Element index is out of range" userInfo:nil]; + } + if(elementIndex == 0){ + bezier[0] = self.firstPoint; + bezier[1] = self.firstPoint; + bezier[2] = self.firstPoint; + bezier[3] = self.firstPoint; + return; + } + + CGPathElement previousElement = [self elementAtIndex:elementIndex-1]; + CGPathElement thisElement = [self elementAtIndex:elementIndex]; + + if(previousElement.type == kCGPathElementMoveToPoint || + previousElement.type == kCGPathElementAddLineToPoint){ + bezier[0] = previousElement.points[0]; + }else if(previousElement.type == kCGPathElementAddQuadCurveToPoint){ + bezier[0] = previousElement.points[1]; + }else if(previousElement.type == kCGPathElementAddCurveToPoint){ + bezier[0] = previousElement.points[2]; + } + + if(thisElement.type == kCGPathElementCloseSubpath){ + bezier[1] = bezier[0]; + bezier[2] = self.firstPoint; + bezier[3] = self.firstPoint; + }else if (thisElement.type == kCGPathElementMoveToPoint || + thisElement.type == kCGPathElementAddLineToPoint){ +// bezier[1] = CGPointMake(bezier[0].x + (thisElement.points[0].x - bezier[0].x)/3, +// bezier[0].y + (thisElement.points[0].y - bezier[0].y)/3); +// bezier[2] = CGPointMake(bezier[0].x + (thisElement.points[0].x - bezier[0].x)*2/3, +// bezier[0].y + (thisElement.points[0].y - bezier[0].y)*2/3); + bezier[1] = bezier[0]; + bezier[2] = thisElement.points[0]; + bezier[3] = thisElement.points[0]; + }else if (thisElement.type == kCGPathElementAddQuadCurveToPoint){ + bezier[1] = thisElement.points[0]; + bezier[2] = thisElement.points[0]; + bezier[3] = thisElement.points[1]; + }else if (thisElement.type == kCGPathElementAddCurveToPoint){ + bezier[1] = thisElement.points[0]; + bezier[2] = thisElement.points[1]; + bezier[3] = thisElement.points[2]; + } +} + +- (CGPoint) tangentOnPathAtElement:(NSInteger)elementIndex andTValue:(CGFloat)tVal{ + if(elementIndex >= [self elementCount] || elementIndex < 0){ + @throw [NSException exceptionWithName:@"BezierElementException" reason:@"Element index is out of range" userInfo:nil]; + } + if(elementIndex == 0){ + return self.firstPoint; + } + + CGPoint bezier[4]; + + [self fillBezier:bezier forElement:elementIndex]; + return [UIBezierPath tangentAtT:tVal forBezier:bezier]; +} + + + ++(CGPoint) pointAtT:(CGFloat)t forBezier:(CGPoint*)bez{ + return bezierPointAtT(bez, t); +} + ++(CGPoint) tangentAtT:(CGFloat)t forBezier:(CGPoint*)bez{ + return bezierTangentAtT(bez, t); +} + + + + + + + + +#pragma mark - Swizzle + +/////////////////////////////////////////////////////////////////////////// +// +// All of these methods are to listen to UIBezierPath method calls +// so that we can add new functionality on top of them without +// changing any of the default behavior. +// +// These methods help maintain: +// 1. the cached flat version of this path +// 2. the flag for if this path is already flat or not + + +-(void) ahmed_swizzle_dealloc{ + objc_setAssociatedObject(self, &BEZIER_PROPERTIES, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self ahmed_swizzle_dealloc]; +} + +- (id)swizzle_initWithCoder:(NSCoder *)decoder{ + self = [self swizzle_initWithCoder:decoder]; + UIBezierPathProperties* props = [decoder decodeObjectForKey:@"pathProperties"]; + objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return self; +} + +-(void) swizzle_encodeWithCoder:(NSCoder *)aCoder{ + [self swizzle_encodeWithCoder:aCoder]; + [aCoder encodeObject:self.pathProperties forKey:@"pathProperties"]; +} +-(void) ahmed_swizzle_applyTransform:(CGAffineTransform)transform{ + // reset our path properties + BOOL isClosed = [self pathProperties].isClosed; + UIBezierPathProperties* props = [[[UIBezierPathProperties alloc] init] autorelease]; + props.isClosed = isClosed; + objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self ahmed_swizzle_applyTransform:transform]; +} + +-(void) swizzle_moveToPoint:(CGPoint)point{ + UIBezierPathProperties* props = [self pathProperties]; + props.bezierPathByFlatteningPath = nil; + BOOL isEmpty = [self isEmpty]; + if(isEmpty || props.isFlat){ + props.isFlat = YES; + } + if(isEmpty){ + props.hasFirstPoint = YES; + props.firstPoint = point; + props.cachedElementCount = 1; + }else if(props.cachedElementCount){ + if(!props.lastAddedElementWasMoveTo){ + // when adding multiple moveTo elements to a path + // in a row, iOS actually just modifies the last moveTo + // instead of having tons of useless moveTos + props.cachedElementCount = props.cachedElementCount + 1; + }else if(props.cachedElementCount == 1){ + // otherwise, the first and only point was + // a move to, so update our first point + props.firstPoint = point; + } + } + props.hasLastPoint = YES; + props.lastPoint = point; + props.tangentAtEnd = 0; + props.lastAddedElementWasMoveTo = YES; + [self swizzle_moveToPoint:point]; +} +-(void) swizzle_addLineToPoint:(CGPoint)point{ + UIBezierPathProperties* props = [self pathProperties]; + props.lastAddedElementWasMoveTo = NO; + props.bezierPathByFlatteningPath = nil; + if([self isEmpty] || props.isFlat){ + props.isFlat = YES; + } + if(props.cachedElementCount){ + props.cachedElementCount = props.cachedElementCount + 1; + } + props.tangentAtEnd = [self calculateTangentBetween:point andPoint:props.lastPoint]; + props.hasLastPoint = YES; + props.lastPoint = point; + [self swizzle_addLineToPoint:point]; +} +-(void) swizzle_addCurveToPoint:(CGPoint)point controlPoint1:(CGPoint)ctrl1 controlPoint2:(CGPoint)ctrl2{ + UIBezierPathProperties* props = [self pathProperties]; + props.lastAddedElementWasMoveTo = NO; + props.bezierPathByFlatteningPath = nil; + if([self isEmpty] || props.isFlat){ + props.isFlat = NO; + } + if(props.cachedElementCount){ + props.cachedElementCount = props.cachedElementCount + 1; + } + props.tangentAtEnd = [self calculateTangentBetween:point andPoint:ctrl2]; + props.hasLastPoint = YES; + props.lastPoint = point; + [self swizzle_addCurveToPoint:point controlPoint1:ctrl1 controlPoint2:ctrl2]; +} +-(void) swizzle_quadCurveToPoint:(CGPoint)point controlPoint:(CGPoint)ctrl1{ + UIBezierPathProperties* props = [self pathProperties]; + props.lastAddedElementWasMoveTo = NO; + props.bezierPathByFlatteningPath = nil; + if([self isEmpty] || props.isFlat){ + props.isFlat = NO; + } + if(props.cachedElementCount){ + props.cachedElementCount = props.cachedElementCount + 1; + } + props.hasLastPoint = YES; + props.lastPoint = point; + props.tangentAtEnd = [self calculateTangentBetween:point andPoint:ctrl1]; + [self swizzle_quadCurveToPoint:point controlPoint:ctrl1]; +} +-(void) swizzle_closePath{ + UIBezierPathProperties* props = [self pathProperties]; + props.isClosed = YES; + props.knowsIfClosed = YES; + props.lastAddedElementWasMoveTo = NO; + props.bezierPathByFlatteningPath = nil; + if([self isEmpty] || props.isFlat){ + props.isFlat = YES; + } + if(props.cachedElementCount){ + props.cachedElementCount = props.cachedElementCount + 1; + } + if(props.hasLastPoint && props.hasFirstPoint){ + props.lastPoint = props.firstPoint; + }else{ + props.hasLastPoint = NO; + } + [self swizzle_closePath]; +} +-(void)swizzle_arcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise{ + UIBezierPathProperties* props = [self pathProperties]; + props.lastAddedElementWasMoveTo = NO; + props.bezierPathByFlatteningPath = nil; + if([self isEmpty] || props.isFlat){ + props.isFlat = NO; + } + if(props.cachedElementCount){ + props.cachedElementCount = props.cachedElementCount + 1; + } + [self swizzle_arcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:clockwise]; +} +-(void) swizzle_removeAllPoints{ + UIBezierPathProperties* props = [self pathProperties]; + props.lastAddedElementWasMoveTo = NO; + props.bezierPathByFlatteningPath = nil; + [self swizzle_removeAllPoints]; + props.cachedElementCount = 0; + props.tangentAtEnd = 0; + props.hasLastPoint = NO; + props.hasFirstPoint = NO; + props.isClosed = NO; + props.knowsIfClosed = YES; + if([self isEmpty] || props.isFlat){ + props.isFlat = YES; + } +} +- (void)swizzle_appendPath:(UIBezierPath *)bezierPath{ + UIBezierPathProperties* props = [self pathProperties]; + props.lastAddedElementWasMoveTo = NO; + UIBezierPathProperties* bezierPathProps = [bezierPath pathProperties]; + props.bezierPathByFlatteningPath = nil; + if(([self isEmpty] && bezierPathProps.isFlat) || (props.isFlat && bezierPathProps.isFlat)){ + props.isFlat = YES; + }else{ + props.isFlat = NO; + } + [self swizzle_appendPath:bezierPath]; + props.hasLastPoint = bezierPathProps.hasLastPoint; + props.lastPoint = bezierPathProps.lastPoint; + props.tangentAtEnd = bezierPathProps.tangentAtEnd; + props.cachedElementCount = 0; +} +-(UIBezierPath*) swizzle_copy{ + UIBezierPathProperties* props = [self pathProperties]; + UIBezierPath* ret = [self swizzle_copy]; + CGMutablePathRef pathRef = CGPathCreateMutableCopy(self.CGPath); + ret.CGPath = pathRef; + CGPathRelease(pathRef); + UIBezierPathProperties* retProps = [ret pathProperties]; + retProps.lastAddedElementWasMoveTo = props.lastAddedElementWasMoveTo; + retProps.isFlat = props.isFlat; + retProps.hasLastPoint = props.hasLastPoint; + retProps.lastPoint = props.lastPoint; + retProps.hasFirstPoint = props.hasFirstPoint; + retProps.firstPoint = props.firstPoint; + retProps.tangentAtEnd = props.tangentAtEnd; + retProps.cachedElementCount = props.cachedElementCount; + retProps.isClosed = props.isClosed; + return ret; +} + ++(UIBezierPath*) swizzle_bezierPathWithRect:(CGRect)rect{ + UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRect:rect]; + UIBezierPathProperties* props = [path pathProperties]; + props.isFlat = YES; + props.knowsIfClosed = YES; + props.isClosed = YES; + return path; +} + ++(UIBezierPath*) swizzle_bezierPathWithOvalInRect:(CGRect)rect{ + UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithOvalInRect:rect]; + UIBezierPathProperties* props = [path pathProperties]; + props.isFlat = YES; + props.knowsIfClosed = YES; + props.isClosed = YES; + return path; +} + ++(UIBezierPath*) swizzle_bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii{ + UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:cornerRadii]; + UIBezierPathProperties* props = [path pathProperties]; + props.isFlat = YES; + props.knowsIfClosed = YES; + props.isClosed = YES; + return path; +} + ++(UIBezierPath*) swizzle_bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadii{ + UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRoundedRect:rect cornerRadius:cornerRadii]; + UIBezierPathProperties* props = [path pathProperties]; + props.isFlat = YES; + props.knowsIfClosed = YES; + props.isClosed = YES; + return path; +} + + + ++(void)load{ + @autoreleasepool { + NSError *error = nil; + [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRect:) + withClassMethod:@selector(swizzle_bezierPathWithRect:) + error:&error]; + [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithOvalInRect:) + withClassMethod:@selector(swizzle_bezierPathWithOvalInRect:) + error:&error]; + [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:) + withClassMethod:@selector(swizzle_bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:) + error:&error]; + [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRoundedRect:cornerRadius:) + withClassMethod:@selector(swizzle_bezierPathWithRoundedRect:cornerRadius:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(moveToPoint:) + withMethod:@selector(swizzle_moveToPoint:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(addLineToPoint:) + withMethod:@selector(swizzle_addLineToPoint:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(addCurveToPoint:controlPoint1:controlPoint2:) + withMethod:@selector(swizzle_addCurveToPoint:controlPoint1:controlPoint2:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(addQuadCurveToPoint:controlPoint:) + withMethod:@selector(swizzle_quadCurveToPoint:controlPoint:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(closePath) + withMethod:@selector(swizzle_closePath) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(addArcWithCenter:radius:startAngle:endAngle:clockwise:) + withMethod:@selector(swizzle_arcWithCenter:radius:startAngle:endAngle:clockwise:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(removeAllPoints) + withMethod:@selector(swizzle_removeAllPoints) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(appendPath:) + withMethod:@selector(swizzle_appendPath:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(copy) + withMethod:@selector(swizzle_copy) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(initWithCoder:) + withMethod:@selector(swizzle_initWithCoder:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(encodeWithCoder:) + withMethod:@selector(swizzle_encodeWithCoder:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(applyTransform:) + withMethod:@selector(ahmed_swizzle_applyTransform:) + error:&error]; + [UIBezierPath jr_swizzleMethod:@selector(dealloc) + withMethod:@selector(ahmed_swizzle_dealloc) + error:&error]; + } +} + + + +/** + * if a curve, the ctrlpoint should be point1, and end point is point2 + * if line, prev point is point1, and end point is point2 + */ +- (CGFloat) calculateTangentBetween:(CGPoint)point1 andPoint:(CGPoint)point2{ + return atan2f( point1.y - point2.y, point1.x - point2.x ); +} + + + +/** + * calculate the point on a bezier at time t + * where 0 < t < 1 + */ +CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t) +{ + CGPoint q; + CGFloat mt = 1 - t; + + CGPoint bez1[4]; + CGPoint bez2[4]; + + q.x = mt * bez[1].x + t * bez[2].x; + q.y = mt * bez[1].y + t * bez[2].y; + bez1[1].x = mt * bez[0].x + t * bez[1].x; + bez1[1].y = mt * bez[0].y + t * bez[1].y; + bez2[2].x = mt * bez[2].x + t * bez[3].x; + bez2[2].y = mt * bez[2].y + t * bez[3].y; + + bez1[2].x = mt * bez1[1].x + t * q.x; + bez1[2].y = mt * bez1[1].y + t * q.y; + bez2[1].x = mt * q.x + t * bez2[2].x; + bez2[1].y = mt * q.y + t * bez2[2].y; + + bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x; + bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y; + + return CGPointMake(bez1[3].x, bez1[3].y); +} + +@end + diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h new file mode 100644 index 000000000..aaa7d3489 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h @@ -0,0 +1,26 @@ +// +// UIBezierPath+Performance_Private.h +// PerformanceBezier +// +// Created by Adam Wulf on 10/16/12. +// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. +// + +#ifndef PerformanceBezier_UIBezierPath_Performance_Private_h +#define PerformanceBezier_UIBezierPath_Performance_Private_h + +#import "UIBezierPathProperties.h" + +@interface UIBezierPath (Performance_Private) + +// helper functions for finding points and tangents +// on a bezier curve +CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t); +CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t); +CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d); +CGFloat dotProduct(const CGPoint p1, const CGPoint p2); + +@end + + +#endif diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h new file mode 100644 index 000000000..b667e35cb --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h @@ -0,0 +1,35 @@ +// +// UIBezierPath+Ahmed.h +// PerformanceBezier +// +// Created by Adam Wulf on 10/6/12. +// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. +// +// +// +// +// This category is motivated by the masters thesis of Athar Ahmad +// available at http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf +// +// More information available at +// http://www.cis.usouthal.edu/~hain/general/Thesis.htm +// +// subdivide code license in included SubdiviceLicense file + +#import +#include + + + +@interface UIBezierPath (Trim) + +-(UIBezierPath*) bezierPathByTrimmingElement:(NSInteger)elementIndex fromTValue:(double)fromTValue toTValue:(double)toTValue; +-(UIBezierPath*) bezierPathByTrimmingToElement:(NSInteger)elementIndex andTValue:(double)tValue; +-(UIBezierPath*) bezierPathByTrimmingFromElement:(NSInteger)elementIndex andTValue:(double)tValue; + ++(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atT:(CGFloat)t; + ++(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atLength:(CGFloat)length withAcceptableError:(CGFloat)acceptableError withCache:(CGFloat*) subBezierlengthCache; + + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m new file mode 100644 index 000000000..c397993d9 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m @@ -0,0 +1,209 @@ +// +// UIBezierPath+Trim.m +// PerformanceBezier +// +// Created by Adam Wulf on 10/6/12. +// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. +// + +#import "UIBezierPath+Trim.h" +#import +#import + +@implementation UIBezierPath (Trim) + +/** + * this will trim a specific element from a tvalue to a tvalue + */ +-(UIBezierPath*) bezierPathByTrimmingElement:(NSInteger)elementIndex fromTValue:(double)fromTValue toTValue:(double)toTValue{ + __block CGPoint previousEndpoint; + __block UIBezierPath* outputPath = [UIBezierPath bezierPath]; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ + if(currentIndex < elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + previousEndpoint = element.points[2]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + previousEndpoint = element.points[0]; + } + }else if(currentIndex == elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + [outputPath moveToPoint:element.points[0]]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + CGPoint bez[4]; + bez[0] = previousEndpoint; + bez[1] = element.points[0]; + bez[2] = element.points[1]; + bez[3] = element.points[2]; + + previousEndpoint = element.points[2]; + + CGPoint left[4], right[4]; + subdivideBezierAtT(bez, left, right, toTValue); + bez[0] = left[0]; + bez[1] = left[1]; + bez[2] = left[2]; + bez[3] = left[3]; + subdivideBezierAtT(bez, left, right, fromTValue / toTValue); + [outputPath moveToPoint:right[0]]; + [outputPath addCurveToPoint:right[3] controlPoint1:right[1] controlPoint2:right[2]]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + CGPoint startPoint = CGPointMake(previousEndpoint.x + fromTValue * (element.points[0].x - previousEndpoint.x), + previousEndpoint.y + fromTValue * (element.points[0].y - previousEndpoint.y)); + CGPoint endPoint = CGPointMake(previousEndpoint.x + toTValue * (element.points[0].x - previousEndpoint.x), + previousEndpoint.y + toTValue * (element.points[0].y - previousEndpoint.y)); + previousEndpoint = element.points[0]; + [outputPath moveToPoint:startPoint]; + [outputPath addLineToPoint:endPoint]; + } + } + }]; + + return outputPath; +} + + + + +/** + * this will trim a uibezier path from the input element index + * and that element's tvalue. it will return all elements after + * that input + */ +-(UIBezierPath*) bezierPathByTrimmingFromElement:(NSInteger)elementIndex andTValue:(double)tValue{ + __block CGPoint previousEndpoint; + __block UIBezierPath* outputPath = [UIBezierPath bezierPath]; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ + if(currentIndex < elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + previousEndpoint = element.points[2]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + previousEndpoint = element.points[0]; + } + }else if(currentIndex == elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + [outputPath moveToPoint:element.points[0]]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + CGPoint bez[4]; + bez[0] = previousEndpoint; + bez[1] = element.points[0]; + bez[2] = element.points[1]; + bez[3] = element.points[2]; + + previousEndpoint = element.points[2]; + + CGPoint left[4], right[4]; + subdivideBezierAtT(bez, left, right, tValue); + [outputPath moveToPoint:right[0]]; + [outputPath addCurveToPoint:right[3] controlPoint1:right[1] controlPoint2:right[2]]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + CGPoint startPoint = CGPointMake(previousEndpoint.x + tValue * (element.points[0].x - previousEndpoint.x), + previousEndpoint.y + tValue * (element.points[0].y - previousEndpoint.y)); + previousEndpoint = element.points[0]; + [outputPath moveToPoint:startPoint]; + [outputPath addLineToPoint:element.points[0]]; + } + }else if(currentIndex > elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + [outputPath moveToPoint:element.points[0]]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + previousEndpoint = element.points[2]; + [outputPath addCurveToPoint:element.points[2] controlPoint1:element.points[0] controlPoint2:element.points[1]]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + previousEndpoint = element.points[0]; + [outputPath addLineToPoint:element.points[0]]; + } + } + }]; + + return outputPath; +} + +/** + * this will trim a uibezier path to the input element index + * and that element's tvalue. it will return all elements before + * that input + */ +-(UIBezierPath*) bezierPathByTrimmingToElement:(NSInteger)elementIndex andTValue:(double)tValue{ + __block CGPoint previousEndpoint; + __block UIBezierPath* outputPath = [UIBezierPath bezierPath]; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ + if(currentIndex == elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + [outputPath moveToPoint:element.points[0]]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + CGPoint bez[4]; + bez[0] = previousEndpoint; + bez[1] = element.points[0]; + bez[2] = element.points[1]; + bez[3] = element.points[2]; + + previousEndpoint = element.points[2]; + + CGPoint left[4], right[4]; + subdivideBezierAtT(bez, left, right, tValue); + [outputPath addCurveToPoint:left[3] controlPoint1:left[1] controlPoint2:left[2]]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + CGPoint endPoint = CGPointMake(previousEndpoint.x + tValue * (element.points[0].x - previousEndpoint.x), + previousEndpoint.y + tValue * (element.points[0].y - previousEndpoint.y)); + previousEndpoint = element.points[0]; + [outputPath addLineToPoint:endPoint]; + } + }else if(currentIndex < elementIndex){ + if(element.type == kCGPathElementMoveToPoint){ + // moveto + previousEndpoint = element.points[0]; + [outputPath moveToPoint:element.points[0]]; + }else if(element.type == kCGPathElementAddCurveToPoint ){ + // curve + previousEndpoint = element.points[2]; + [outputPath addCurveToPoint:element.points[2] controlPoint1:element.points[0] controlPoint2:element.points[1]]; + }else if(element.type == kCGPathElementAddLineToPoint){ + // line + previousEndpoint = element.points[0]; + [outputPath addLineToPoint:element.points[0]]; + } + } + }]; + + return outputPath; +} + ++(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atT:(CGFloat)t{ + subdivideBezierAtT(bez, bez1, bez2, t); +} + ++(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2{ + subdivideBezierAtT(bez, bez1, bez2, .5); +} + ++(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atLength:(CGFloat)length withAcceptableError:(CGFloat)acceptableError withCache:(CGFloat*) subBezierlengthCache{ + subdivideBezierAtLengthWithCache(bez, bez1, bez2, length, acceptableError,subBezierlengthCache); +} + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h new file mode 100644 index 000000000..323ad9ccb --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h @@ -0,0 +1,33 @@ +// +// UIBezierPath+DKFix.h +// ClippingBezier +// +// Created by Adam Wulf on 5/9/15. +// +// + +#import + +@interface UIBezierPath (Trimming) + +-(void) appendPathRemovingInitialMoveToPoint:(UIBezierPath*)otherPath; + +-(NSArray*) subPaths; + +-(NSInteger) countSubPaths; + +- (NSInteger) subpathIndexForElement:(NSInteger) element; + +- (CGFloat) length; + +- (CGFloat) tangentAtStart; + +- (CGFloat) tangentAtStartOfSubpath:(NSInteger)index; + +- (UIBezierPath*) bezierPathByTrimmingFromLength:(CGFloat)trimLength; + +- (UIBezierPath*) bezierPathByTrimmingToLength:(CGFloat)trimLength; + +- (UIBezierPath*) bezierPathByTrimmingToLength:(CGFloat)trimLength withMaximumError:(CGFloat)err; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m new file mode 100644 index 000000000..0b474c00a --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m @@ -0,0 +1,365 @@ +// +// UIBezierPath+DKFix.m +// ClippingBezier +// +// Created by Adam Wulf on 5/9/15. +// +// + +#import "UIBezierPath+Trimming.h" +#import +#pragma mark - Subdivide helpers by Alastair J. Houghton +/* + * Bezier path utility category (trimming) + * + * (c) 2004 Alastair J. Houghton + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author of this software may not be used to endorse + * or promote products derived from the software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + +@implementation UIBezierPath (Trimming) + +-(NSInteger) countSubPaths{ + return [[self subPaths] count]; +} + +/* Return an NSBezierPath corresponding to the first trimLength units + of this NSBezierPath. */ +- (UIBezierPath *)bezierPathByTrimmingToLength:(CGFloat)trimLength + withMaximumError:(CGFloat)maxError +{ + UIBezierPath *newPath = [UIBezierPath bezierPath]; + NSInteger elements = [self elementCount]; + int n; + double length = 0.0; + CGPoint pointForClose = CGPointMake(0.0, 0.0); + CGPoint lastPoint = CGPointMake (0.0, 0.0); + + for (n = 0; n < elements; ++n) { + CGPoint points[3]; + CGPathElement element = [self elementAtIndex:n + associatedPoints:points]; + double elementLength; + double remainingLength = trimLength - length; + + if(remainingLength == 0){ + break; + } + + switch (element.type) { + case kCGPathElementMoveToPoint: + [newPath moveToPoint:points[0]]; + pointForClose = lastPoint = points[0]; + continue; + + case kCGPathElementAddLineToPoint: + elementLength = distance (lastPoint, points[0]); + + if (length + elementLength <= trimLength) + [newPath addLineToPoint:points[0]]; + else { + double f = remainingLength / elementLength; + [newPath addLineToPoint:CGPointMake (lastPoint.x + + f * (points[0].x - lastPoint.x), + lastPoint.y + + f * (points[0].y - lastPoint.y))]; + return newPath; + } + + length += elementLength; + lastPoint = points[0]; + break; + + case kCGPathElementAddCurveToPoint: + case kCGPathElementAddQuadCurveToPoint: { + CGPoint bezier[4]; + if(element.type == kCGPathElementAddCurveToPoint){ + bezier[0] = lastPoint; + bezier[1] = points[0]; + bezier[2] = points[1]; + bezier[3] = points[2]; + }else{ + bezier[0] = lastPoint; + bezier[1] = points[0]; + bezier[2] = points[0]; + bezier[3] = points[1]; + } + elementLength = lengthOfBezier (bezier, maxError); + + if (length + elementLength <= trimLength) + [newPath addCurveToPoint:points[2] controlPoint1:points[0] controlPoint2:points[1]]; + else { + CGPoint bez1[4], bez2[4]; + subdivideBezierAtLength (bezier, bez1, bez2, + remainingLength, maxError); + [newPath addCurveToPoint:bez1[3] controlPoint1:bez1[1] controlPoint2:bez1[2]]; + return newPath; + } + + length += elementLength; + lastPoint = points[2]; + break; + } + + case kCGPathElementCloseSubpath: + elementLength = distance (lastPoint, pointForClose); + + if (length + elementLength <= trimLength) + [newPath closePath]; + else { + double f = remainingLength / elementLength; + [newPath addLineToPoint:CGPointMake(lastPoint.x + + f * (points[0].x - lastPoint.x), + lastPoint.y + + f * (points[0].y - lastPoint.y))]; + return newPath; + } + + length += elementLength; + lastPoint = pointForClose; + break; + } + } + + return newPath; +} + +// Convenience method +- (UIBezierPath *)bezierPathByTrimmingToLength:(CGFloat)trimLength +{ + return [self bezierPathByTrimmingToLength:trimLength withMaximumError:0.1]; +} + +/* Return an NSBezierPath corresponding to the part *after* the first + trimLength units of this NSBezierPath. */ +- (UIBezierPath *)bezierPathByTrimmingFromLength:(CGFloat)trimLength + withMaximumError:(CGFloat)maxError +{ + UIBezierPath *newPath = [UIBezierPath bezierPath]; + NSInteger elements = [self elementCount]; + int n; + double length = 0.0; + CGPoint pointForClose = CGPointMake (0.0, 0.0); + CGPoint lastPoint = CGPointMake (0.0, 0.0); + BOOL legitMoveTo = NO; + + for (n = 0; n < elements; ++n) { + CGPoint points[3]; + CGPathElement element = [self elementAtIndex:n + associatedPoints:points]; + double elementLength; + double remainingLength = trimLength - length; + + switch (element.type) { + case kCGPathElementMoveToPoint: + if(remainingLength < 0){ + [newPath moveToPoint:points[0]]; + legitMoveTo = YES; + } + pointForClose = lastPoint = points[0]; + continue; + + case kCGPathElementAddLineToPoint: + elementLength = distance (lastPoint, points[0]); + + if (length > trimLength){ + [newPath addLineToPoint:points[0]]; + }else if (length + elementLength > trimLength) { + double f = remainingLength / elementLength; + [newPath moveToPoint:CGPointMake (lastPoint.x + + f * (points[0].x - lastPoint.x), + lastPoint.y + + f * (points[0].y - lastPoint.y))]; + [newPath addLineToPoint:points[0]]; + } + + length += elementLength; + lastPoint = points[0]; + break; + + case kCGPathElementAddCurveToPoint: + case kCGPathElementAddQuadCurveToPoint: { + CGPoint bezier[4]; + if(element.type == kCGPathElementAddCurveToPoint){ + bezier[0] = lastPoint; + bezier[1] = points[0]; + bezier[2] = points[1]; + bezier[3] = points[2]; + }else{ + bezier[0] = lastPoint; + bezier[1] = points[0]; + bezier[2] = points[0]; + bezier[3] = points[1]; + } + elementLength = lengthOfBezier (bezier, maxError); + + if (length > trimLength){ + [newPath addCurveToPoint:points[2] + controlPoint1:points[0] + controlPoint2:points[1]]; + }else if (length + elementLength > trimLength) { + CGPoint bez1[4], bez2[4]; + subdivideBezierAtLength (bezier, bez1, bez2, + remainingLength, maxError); + [newPath moveToPoint:bez2[0]]; + [newPath addCurveToPoint:bez2[3] + controlPoint1:bez2[1] + controlPoint2:bez2[2]]; + } + + length += elementLength; + lastPoint = points[2]; + break; + } + + case kCGPathElementCloseSubpath: + elementLength = distance (lastPoint, pointForClose); + + if (length > trimLength){ + if(legitMoveTo){ + [newPath closePath]; + }else{ + [newPath addLineToPoint:pointForClose]; + } + } else if (length + elementLength > trimLength) { + double f = remainingLength / elementLength; + [newPath moveToPoint:CGPointMake (lastPoint.x + + f * (points[0].x - lastPoint.x), + lastPoint.y + + f * (points[0].y - lastPoint.y))]; + [newPath addLineToPoint:points[0]]; + } + + length += elementLength; + lastPoint = pointForClose; + break; + } + } + + return newPath; +} + +// Convenience method +- (UIBezierPath *)bezierPathByTrimmingFromLength:(CGFloat)trimLength +{ + return [self bezierPathByTrimmingFromLength:trimLength withMaximumError:0.1]; +} + +- (NSInteger) subpathIndexForElement:(NSInteger) element{ + __block NSInteger subpathIndex = -1; + __block BOOL lastWasMoveTo = NO; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { + if(element.type == kCGPathElementMoveToPoint){ + if(!lastWasMoveTo){ + subpathIndex += 1; + } + lastWasMoveTo = YES; + }else{ + lastWasMoveTo = NO; + } + }]; + return subpathIndex; +} + +- (CGFloat) length{ + __block CGFloat length = 0; + __block CGPoint lastMoveToPoint = CGPointNotFound; + __block CGPoint lastElementEndPoint = CGPointNotFound; + [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { + if(element.type == kCGPathElementMoveToPoint){ + lastElementEndPoint = element.points[0]; + lastMoveToPoint = element.points[0]; + }else if(element.type == kCGPathElementCloseSubpath){ + length += distance(lastElementEndPoint, lastMoveToPoint); + lastElementEndPoint = lastMoveToPoint; + }else if(element.type == kCGPathElementAddLineToPoint){ + length += distance(lastElementEndPoint, element.points[0]); + lastElementEndPoint = element.points[0]; + }else if(element.type == kCGPathElementAddQuadCurveToPoint || + element.type == kCGPathElementAddCurveToPoint){ + + CGPoint bez[4]; + bez[0] = lastElementEndPoint; + + if(element.type == kCGPathElementAddQuadCurveToPoint){ + bez[1] = element.points[0]; + bez[2] = element.points[0]; + bez[3] = element.points[1]; + lastElementEndPoint = element.points[1]; + }else if(element.type == kCGPathElementAddCurveToPoint){ + bez[1] = element.points[0]; + bez[2] = element.points[1]; + bez[3] = element.points[2]; + lastElementEndPoint = element.points[2]; + } + + length += lengthOfBezier(bez, .5);; + } + }]; + return length; +} + +- (CGFloat) tangentAtStart{ + if([self elementCount] < 2){ + return 0.0; + } + + CGPathElement ele1 = [self elementAtIndex:0]; + CGPathElement ele2 = [self elementAtIndex:1]; + + if(ele1.type != kCGPathElementMoveToPoint){ + return 0.0; + } + + CGPoint point1 = ele1.points[0]; + CGPoint point2 = CGPointZero; + + switch (ele2.type) { + case kCGPathElementMoveToPoint: + return 0.0; + break; + case kCGPathElementAddCurveToPoint: + case kCGPathElementAddQuadCurveToPoint: + case kCGPathElementAddLineToPoint: + point2 = ele2.points[0]; + break; + case kCGPathElementCloseSubpath: + return 0.0; + break; + } + + return atan2f( point2.y - point1.y, point2.x - point1.x ) + M_PI; +} + +- (CGFloat) tangentAtStartOfSubpath:(NSInteger)index{ + return [[[self subPaths] objectAtIndex:index] tangentAtStart]; +} + + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h new file mode 100644 index 000000000..0ba08f331 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h @@ -0,0 +1,17 @@ +// +// UIBezierPath+Uncached.h +// PerformanceBezier +// +// Created by Adam Wulf on 2/6/15. +// +// + +#import + +@interface UIBezierPath (Uncached) + +#ifdef MMPreventBezierPerformance +-(void) simulateNoBezierCaching; +#endif + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m new file mode 100644 index 000000000..791bfc51e --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m @@ -0,0 +1,23 @@ +// +// UIBezierPath+Uncached.m +// PerformanceBezier +// +// Created by Adam Wulf on 2/6/15. +// +// + +#import "UIBezierPath+Uncached.h" +#import "UIBezierPath+NSOSX.h" + +@implementation UIBezierPath (Uncached) + + +#ifdef MMPreventBezierPerformance +-(void) simulateNoBezierCaching{ + [self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){ + // noop + }]; +} +#endif + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h new file mode 100644 index 000000000..b7b1017b4 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h @@ -0,0 +1,48 @@ +// +// UIBezierPath+Util.h +// PerformanceBezier +// +// Created by Adam Wulf on 5/20/15. +// +// + +#import + +@interface UIBezierPath (Util) + ++(CGFloat) lengthOfBezier:(const CGPoint[4])bez withAccuracy:(CGFloat)accuracy; + +@end + +#if defined __cplusplus +extern "C" { +#endif + + // simple helper function to return the distance of a point to a line + CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end); + + // returns the distance between two points + CGFloat distance(const CGPoint p1, const CGPoint p2); + + void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t); + + CGFloat subdivideBezierAtLength (const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat length, CGFloat acceptableError); + + CGFloat subdivideBezierAtLengthWithCache(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat length, CGFloat acceptableError, + CGFloat* subBezierLengthCache); + + CGFloat lengthOfBezier(const CGPoint bez[4], CGFloat acceptableError); + + + CGPoint lineSegmentIntersection(CGPoint A, CGPoint B, CGPoint C, CGPoint D); + + CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t); + + CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d); + + + +#if defined __cplusplus +}; +#endif + diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m new file mode 100644 index 000000000..5688f8a82 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m @@ -0,0 +1,316 @@ +// +// UIBezierPath+Util.m +// PerformanceBezier +// +// Created by Adam Wulf on 5/20/15. +// +// + +#import "PerformanceBezier.h" +#import "UIBezierPath+Util.h" +#import "UIBezierPath+Trim.h" +#import "UIBezierPath+Performance.h" + +@implementation UIBezierPath (Util) + ++(CGFloat) lengthOfBezier:(const CGPoint[4])bez withAccuracy:(CGFloat)accuracy{ + return lengthOfBezier(bez, accuracy); +} + +#pragma mark - Subdivide helpers by Alastair J. Houghton +/* + * Bezier path utility category (trimming) + * + * (c) 2004 Alastair J. Houghton + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author of this software may not be used to endorse + * or promote products derived from the software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Subdivide a Bézier (50% subdivision) +inline static void subdivideBezier(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4]) +{ + CGPoint q; + + bez1[0].x = bez[0].x; + bez1[0].y = bez[0].y; + bez2[3].x = bez[3].x; + bez2[3].y = bez[3].y; + + q.x = (bez[1].x + bez[2].x) / 2.0; + q.y = (bez[1].y + bez[2].y) / 2.0; + bez1[1].x = (bez[0].x + bez[1].x) / 2.0; + bez1[1].y = (bez[0].y + bez[1].y) / 2.0; + bez2[2].x = (bez[2].x + bez[3].x) / 2.0; + bez2[2].y = (bez[2].y + bez[3].y) / 2.0; + + bez1[2].x = (bez1[1].x + q.x) / 2.0; + bez1[2].y = (bez1[1].y + q.y) / 2.0; + bez2[1].x = (q.x + bez2[2].x) / 2.0; + bez2[1].y = (q.y + bez2[2].y) / 2.0; + + bez1[3].x = bez2[0].x = (bez1[2].x + bez2[1].x) / 2.0; + bez1[3].y = bez2[0].y = (bez1[2].y + bez2[1].y) / 2.0; +} + +// Subdivide a Bézier (specific division) +void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t) +{ + CGPoint q; + CGFloat mt = 1 - t; + + bez1[0].x = bez[0].x; + bez1[0].y = bez[0].y; + bez2[3].x = bez[3].x; + bez2[3].y = bez[3].y; + + q.x = mt * bez[1].x + t * bez[2].x; + q.y = mt * bez[1].y + t * bez[2].y; + bez1[1].x = mt * bez[0].x + t * bez[1].x; + bez1[1].y = mt * bez[0].y + t * bez[1].y; + bez2[2].x = mt * bez[2].x + t * bez[3].x; + bez2[2].y = mt * bez[2].y + t * bez[3].y; + + bez1[2].x = mt * bez1[1].x + t * q.x; + bez1[2].y = mt * bez1[1].y + t * q.y; + bez2[1].x = mt * q.x + t * bez2[2].x; + bez2[1].y = mt * q.y + t * bez2[2].y; + + bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x; + bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y; +} + + +// Length of a curve +CGFloat lengthOfBezier(const CGPoint bez[4], CGFloat acceptableError) +{ + CGFloat polyLen = 0.0; + CGFloat chordLen = distance(bez[0], bez[3]); + CGFloat retLen, errLen; + NSUInteger n; + + for (n = 0; n < 3; ++n) + polyLen += distance(bez[n], bez[n + 1]); + + errLen = polyLen - chordLen; + + if (errLen > acceptableError) { + CGPoint left[4], right[4]; + subdivideBezier (bez, left, right); + retLen = (lengthOfBezier (left, acceptableError) + + lengthOfBezier (right, acceptableError)); + } else { + retLen = 0.5 * (polyLen + chordLen); + } + + return retLen; +} + +// Split a Bézier curve at a specific length +CGFloat subdivideBezierAtLength (const CGPoint bez[4], + CGPoint bez1[4], + CGPoint bez2[4], + CGFloat length, + CGFloat acceptableError) +{ + return subdivideBezierAtLengthWithCache(bez, bez1, bez2, length, acceptableError, NULL); +} + +/** + * will split the input bezier curve at the input length + * within a given margin of error + * + * the two curves will exactly match the original curve + */ +CGFloat subdivideBezierAtLengthWithCache(const CGPoint bez[4], + CGPoint bez1[4], + CGPoint bez2[4], + CGFloat length, + CGFloat acceptableError, + CGFloat* subBezierLengthCache){ + CGFloat top = 1.0, bottom = 0.0; + CGFloat t, prevT; + BOOL needsDealloc = NO; + + if(!subBezierLengthCache){ + subBezierLengthCache = calloc(1000, sizeof(CGFloat)); + needsDealloc = YES; + } + + prevT = t = 0.5; + for (;;) { + CGFloat len1; + + subdivideBezierAtT (bez, bez1, bez2, t); + + int lengthCacheIndex = (int)floorf(t*1000); + len1 = subBezierLengthCache[lengthCacheIndex]; + if(!len1){ + len1 = [UIBezierPath lengthOfBezier:bez1 withAccuracy:0.5 * acceptableError]; + subBezierLengthCache[lengthCacheIndex] = len1; + } + + if (fabs (length - len1) < acceptableError){ + return len1; + } + + if (length > len1) { + bottom = t; + t = 0.5 * (t + top); + } else if (length < len1) { + top = t; + t = 0.5 * (bottom + t); + } + + if (t == prevT){ + subBezierLengthCache[lengthCacheIndex] = len1; + + if(needsDealloc){ + free(subBezierLengthCache); + } + return len1; + } + + prevT = t; + } +} + + + +// public domain function by Darel Rex Finley, 2006 + +// Determines the intersection point of the line segment defined by points A and B +// with the line segment defined by points C and D. +// +// Returns YES if the intersection point was found, and stores that point in X,Y. +// Returns NO if there is no determinable intersection point, in which case X,Y will +// be unmodified. + +CGPoint lineSegmentIntersection(CGPoint A, CGPoint B, CGPoint C, CGPoint D) { + + double distAB, theCos, theSin, newX, ABpos ; + + // Fail if either line segment is zero-length. + if ((A.x==B.x && A.y==B.y) || (C.x==D.x && C.y==D.y)) return CGPointNotFound; + + // Fail if the segments share an end-point. + if ((A.x==C.x && A.y==C.y) || + (B.x==C.x && B.y==C.y) || + (A.x==D.x && A.y==D.y) || + (B.x==D.x && B.y==D.y)) { + return CGPointNotFound; + } + + // (1) Translate the system so that point A is on the origin. + B.x-=A.x; B.y-=A.y; + C.x-=A.x; C.y-=A.y; + D.x-=A.x; D.y-=A.y; + + // Discover the length of segment A-B. + distAB=sqrt(B.x*B.x+B.y*B.y); + + // (2) Rotate the system so that point B is on the positive X axis. + theCos=B.x/distAB; + theSin=B.y/distAB; + newX=C.x*theCos+C.y*theSin; + C.y =C.y*theCos-C.x*theSin; + C.x=newX; + newX=D.x*theCos+D.y*theSin; + D.y =D.y*theCos-D.x*theSin; + D.x=newX; + + // Fail if segment C-D doesn't cross line A-B. + if ((C.y<0. && D.y<0.) || (C.y>=0. && D.y>=0.)) return CGPointNotFound; + + // (3) Discover the position of the intersection point along line A-B. + ABpos=D.x+(C.x-D.x)*D.y/(D.y-C.y); + + // Fail if segment C-D crosses line A-B outside of segment A-B. + if (ABpos<0. || ABpos>distAB) return CGPointNotFound; + + // (4) Apply the discovered position to line A-B in the original coordinate system. + // Success. + return CGPointMake(A.x+ABpos*theCos, A.y+ABpos*theSin); +} + + +#pragma mark - Helper + + +// primary algorithm from: +// http://stackoverflow.com/questions/4089443/find-the-tangent-of-a-point-on-a-cubic-bezier-curve-on-an-iphone +CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t) +{ + return CGPointMake(bezierTangent(t, bez[0].x, bez[1].x, bez[2].x, bez[3].x), + bezierTangent(t, bez[0].y, bez[1].y, bez[2].y, bez[3].y)); +} +CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) +{ + CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a ); + CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) ); + CGFloat C3 = ( (3.0 * b) - (3.0 * a) ); + return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 ); +} + + +/** + * returns the shortest distance from a point to a line + */ +CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end){ + CGPoint v = CGPointMake(end.x - start.x, end.y - start.y); + CGPoint w = CGPointMake(point.x - start.x, point.y - start.y); + CGFloat c1 = dotProduct(w, v); + CGFloat c2 = dotProduct(v, v); + CGFloat d; + if (c1 <= 0) { + d = distance(point, start); + } + else if (c2 <= c1) { + d = distance(point, end); + } + else { + CGFloat b = c1 / c2; + CGPoint Pb = CGPointMake(start.x + b * v.x, start.y + b * v.y); + d = distance(point, Pb); + } + return d; +} +/** + * returns the distance between two points + */ +CGFloat distance(const CGPoint p1, const CGPoint p2) { + return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2)); +} +/** + * returns the dot product of two coordinates + */ +CGFloat dotProduct(const CGPoint p1, const CGPoint p2) { + return p1.x * p2.x + p1.y * p2.y; +} + + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h new file mode 100644 index 000000000..be99d0630 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h @@ -0,0 +1,25 @@ +// +// UIBezierPathProperties.h +// PerformanceBezier +// +// Created by Adam Wulf on 2/1/15. +// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. +// + +#import +#import + +@interface UIBezierPathProperties : NSObject +@property (nonatomic) BOOL isClosed; +@property (nonatomic) BOOL knowsIfClosed; +@property (nonatomic) BOOL isFlat; +@property (nonatomic) BOOL hasLastPoint; +@property (nonatomic) CGPoint lastPoint; +@property (nonatomic) BOOL hasFirstPoint; +@property (nonatomic) CGPoint firstPoint; +@property (nonatomic) CGFloat tangentAtEnd; +@property (nonatomic) NSInteger cachedElementCount; +@property (nonatomic, retain) UIBezierPath* bezierPathByFlatteningPath; +@property (nonatomic) BOOL lastAddedElementWasMoveTo; + +@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m new file mode 100644 index 000000000..bb5e90416 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m @@ -0,0 +1,81 @@ +// +// UIBezierPathProperties.m +// PerformanceBezier +// +// Created by Adam Wulf on 2/1/15. +// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. +// + +#import "UIBezierPathProperties.h" + +@implementation UIBezierPathProperties{ + BOOL isFlat; + BOOL knowsIfClosed; + BOOL isClosed; + BOOL hasLastPoint; + CGPoint lastPoint; + BOOL hasFirstPoint; + CGPoint firstPoint; + CGFloat tangentAtEnd; + NSInteger cachedElementCount; + UIBezierPath* bezierPathByFlatteningPath; +} + +@synthesize isFlat; +@synthesize knowsIfClosed; +@synthesize isClosed; +@synthesize hasLastPoint; +@synthesize lastPoint; +@synthesize tangentAtEnd; +@synthesize cachedElementCount; +@synthesize bezierPathByFlatteningPath; +@synthesize hasFirstPoint; +@synthesize firstPoint; + +- (id)initWithCoder:(NSCoder *)decoder{ + self = [super init]; + if (!self) { + return nil; + } + isFlat = [decoder decodeBoolForKey:@"pathProperties_isFlat"]; + knowsIfClosed = [decoder decodeBoolForKey:@"pathProperties_knowsIfClosed"]; + isClosed = [decoder decodeBoolForKey:@"pathProperties_isClosed"]; + hasLastPoint = [decoder decodeBoolForKey:@"pathProperties_hasLastPoint"]; + lastPoint = [decoder decodeCGPointForKey:@"pathProperties_lastPoint"]; + hasFirstPoint = [decoder decodeBoolForKey:@"pathProperties_hasFirstPoint"]; + firstPoint = [decoder decodeCGPointForKey:@"pathProperties_firstPoint"]; + tangentAtEnd = [decoder decodeFloatForKey:@"pathProperties_tangentAtEnd"]; + cachedElementCount = [decoder decodeIntegerForKey:@"pathProperties_cachedElementCount"]; + return self; +} + +-(void) encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeBool:isFlat forKey:@"pathProperties_isFlat"]; + [aCoder encodeBool:knowsIfClosed forKey:@"pathProperties_knowsIfClosed"]; + [aCoder encodeBool:isClosed forKey:@"pathProperties_isClosed"]; + [aCoder encodeBool:hasLastPoint forKey:@"pathProperties_hasLastPoint"]; + [aCoder encodeCGPoint:lastPoint forKey:@"pathProperties_lastPoint"]; + [aCoder encodeBool:hasFirstPoint forKey:@"pathProperties_hasFirstPoint"]; + [aCoder encodeCGPoint:firstPoint forKey:@"pathProperties_firstPoint"]; + [aCoder encodeFloat:tangentAtEnd forKey:@"pathProperties_tangentAtEnd"]; + [aCoder encodeInteger:cachedElementCount forKey:@"pathProperties_cachedElementCount"]; +} + +// for some reason the iPad 1 on iOS 5 needs to have this +// method coded and not synthesized. +-(void) setBezierPathByFlatteningPath:(UIBezierPath *)_bezierPathByFlatteningPath{ + if(bezierPathByFlatteningPath != _bezierPathByFlatteningPath){ + [bezierPathByFlatteningPath release]; + [_bezierPathByFlatteningPath retain]; + } + bezierPathByFlatteningPath = _bezierPathByFlatteningPath; +} + +-(void) dealloc{ + [bezierPathByFlatteningPath release]; + bezierPathByFlatteningPath = nil; + [super dealloc]; +} + + +@end diff --git a/ios/PerformanceBezier/PerformanceBezierTests/Info.plist b/ios/PerformanceBezier/PerformanceBezierTests/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezierTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h new file mode 100644 index 000000000..775c30aca --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h @@ -0,0 +1,23 @@ +// +// PerformanceBezierAbstractTest.h +// PerformanceBezier +// +// Created by Adam Wulf on 11/20/13. +// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. +// + +#import "PerformanceBezier.h" + +@interface PerformanceBezierAbstractTest : XCTestCase + +@property (nonatomic, readonly) UIBezierPath* complexShape; + +-(CGFloat) round:(CGFloat)val to:(int)digits; + +-(BOOL) point:(CGPoint)p1 isNearTo:(CGPoint)p2; + +-(BOOL) checkTanPoint:(CGFloat) f1 isLessThan:(CGFloat)f2; +-(BOOL) check:(CGFloat) f1 isLessThan:(CGFloat)f2 within:(CGFloat)marginOfError; + +@end + diff --git a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m new file mode 100644 index 000000000..b71201211 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m @@ -0,0 +1,161 @@ +// +// PerformanceBezierAbstractTest.m +// PerformanceBezier +// +// Created by Adam Wulf on 11/20/13. +// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. +// + +#import +#import "PerformanceBezierAbstractTest.h" + +#define kIntersectionPointPrecision .1 + +@implementation PerformanceBezierAbstractTest{ + UIBezierPath* complexShape; +} + +@synthesize complexShape; + +-(void) setUp{ + complexShape = [UIBezierPath bezierPath]; + [complexShape moveToPoint:CGPointMake(218.500000,376.000000)]; + [complexShape addCurveToPoint:CGPointMake(227.000000,362.000000) controlPoint1:CGPointMake(218.100235,369.321564) controlPoint2:CGPointMake(222.816589,365.945923)]; + // [complexShape addCurveToPoint:CGPointMake(213.000000,372.000000) controlPoint1:CGPointMake(237.447144,352.833008) controlPoint2:CGPointMake(255.082840,326.966705)]; + [complexShape addCurveToPoint:CGPointMake(243.000000,316.000000) controlPoint1:CGPointMake(232.801880,363.107697) controlPoint2:CGPointMake(248.582321,338.033752)]; + [complexShape addCurveToPoint:CGPointMake(172.000000,218.000000) controlPoint1:CGPointMake(215.326599,286.866608) controlPoint2:CGPointMake(182.880371,258.374298)]; + [complexShape addCurveToPoint:CGPointMake(242.000000,111.000000) controlPoint1:CGPointMake(149.812820,168.222122) controlPoint2:CGPointMake(195.466583,119.790573)]; + [complexShape addCurveToPoint:CGPointMake(265.000000,109.000000) controlPoint1:CGPointMake(249.474472,108.948883) controlPoint2:CGPointMake(257.298248,108.285248)]; + [complexShape addCurveToPoint:CGPointMake(302.000000,116.000000) controlPoint1:CGPointMake(277.539948,110.401459) controlPoint2:CGPointMake(291.623962,104.844376)]; + [complexShape addCurveToPoint:CGPointMake(310.000000,148.000000) controlPoint1:CGPointMake(310.890778,124.607719) controlPoint2:CGPointMake(310.189972,136.988098)]; + [complexShape addCurveToPoint:CGPointMake(302.000000,184.000000) controlPoint1:CGPointMake(310.722900,160.600952) controlPoint2:CGPointMake(305.109131,172.145187)]; + [complexShape addCurveToPoint:CGPointMake(295.000000,221.000000) controlPoint1:CGPointMake(299.082794,196.217377) controlPoint2:CGPointMake(297.024170,208.612000)]; + [complexShape addCurveToPoint:CGPointMake(304.000000,294.000000) controlPoint1:CGPointMake(297.367493,244.586227) controlPoint2:CGPointMake(286.870880,273.642609)]; + [complexShape addCurveToPoint:CGPointMake(343.000000,305.000000) controlPoint1:CGPointMake(315.167755,304.101868) controlPoint2:CGPointMake(329.526489,302.807587)]; + [complexShape addCurveToPoint:CGPointMake(395.000000,297.000000) controlPoint1:CGPointMake(361.147430,306.169556) controlPoint2:CGPointMake(378.948700,305.707855)]; + [complexShape addCurveToPoint:CGPointMake(413.000000,285.000000) controlPoint1:CGPointMake(401.931854,294.629944) controlPoint2:CGPointMake(408.147980,290.431854)]; + [complexShape addCurveToPoint:CGPointMake(435.000000,255.000000) controlPoint1:CGPointMake(423.555023,278.199219) controlPoint2:CGPointMake(433.437531,268.110229)]; + [complexShape addCurveToPoint:CGPointMake(424.000000,228.000000) controlPoint1:CGPointMake(436.063995,245.070923) controlPoint2:CGPointMake(432.549713,233.803619)]; + [complexShape addCurveToPoint:CGPointMake(402.000000,209.000000) controlPoint1:CGPointMake(416.571869,221.776123) controlPoint2:CGPointMake(409.428101,215.223877)]; + [complexShape addCurveToPoint:CGPointMake(385.000000,183.000000) controlPoint1:CGPointMake(392.239838,203.283615) controlPoint2:CGPointMake(390.379822,191.873459)]; + [complexShape addCurveToPoint:CGPointMake(385.000000,128.000000) controlPoint1:CGPointMake(378.582947,166.163315) controlPoint2:CGPointMake(374.234924,143.960587)]; + [complexShape addCurveToPoint:CGPointMake(472.000000,105.000000) controlPoint1:CGPointMake(405.864136,103.071175) controlPoint2:CGPointMake(442.110016,95.355499)]; + [complexShape addLineToPoint:CGPointMake(472.000000,105.000000)]; + [complexShape addLineToPoint:CGPointMake(488.000000,177.000000)]; + [complexShape addCurveToPoint:CGPointMake(534.000000,177.000000) controlPoint1:CGPointMake(498.942047,187.308975) controlPoint2:CGPointMake(522.349426,190.655228)]; + [complexShape addLineToPoint:CGPointMake(534.000000,177.000000)]; + [complexShape addLineToPoint:CGPointMake(611.000000,77.000000)]; + [complexShape addCurveToPoint:CGPointMake(700.000000,111.000000) controlPoint1:CGPointMake(643.743652,54.625603) controlPoint2:CGPointMake(684.106140,80.752876)]; + [complexShape addCurveToPoint:CGPointMake(704.000000,126.000000) controlPoint1:CGPointMake(703.098755,115.324257) controlPoint2:CGPointMake(704.460693,120.742104)]; + [complexShape addCurveToPoint:CGPointMake(700.000000,156.000000) controlPoint1:CGPointMake(703.210999,136.088333) controlPoint2:CGPointMake(706.451782,146.830612)]; + [complexShape addCurveToPoint:CGPointMake(681.000000,196.000000) controlPoint1:CGPointMake(695.262329,170.166794) controlPoint2:CGPointMake(684.152832,181.086166)]; + [complexShape addCurveToPoint:CGPointMake(655.000000,246.000000) controlPoint1:CGPointMake(672.227966,212.611191) controlPoint2:CGPointMake(663.084045,229.033600)]; + [complexShape addCurveToPoint:CGPointMake(636.000000,300.000000) controlPoint1:CGPointMake(649.778381,264.429260) controlPoint2:CGPointMake(639.110535,280.894592)]; + [complexShape addCurveToPoint:CGPointMake(637.000000,374.000000) controlPoint1:CGPointMake(633.264893,324.262054) controlPoint2:CGPointMake(629.540955,350.094421)]; + [complexShape addCurveToPoint:CGPointMake(692.000000,512.000000) controlPoint1:CGPointMake(666.332947,413.789734) controlPoint2:CGPointMake(699.608032,459.043060)]; + [complexShape addCurveToPoint:CGPointMake(683.000000,536.000000) controlPoint1:CGPointMake(688.852905,519.944031) controlPoint2:CGPointMake(685.852539,527.945801)]; + [complexShape addCurveToPoint:CGPointMake(661.000000,571.000000) controlPoint1:CGPointMake(677.198853,548.592712) controlPoint2:CGPointMake(668.260010,559.289062)]; + [complexShape addCurveToPoint:CGPointMake(610.000000,619.000000) controlPoint1:CGPointMake(648.250000,590.532715) controlPoint2:CGPointMake(631.222351,608.483521)]; + [complexShape addCurveToPoint:CGPointMake(585.000000,625.000000) controlPoint1:CGPointMake(602.312439,623.111694) controlPoint2:CGPointMake(593.647827,625.135620)]; + [complexShape addCurveToPoint:CGPointMake(542.000000,612.000000) controlPoint1:CGPointMake(569.838867,626.477356) controlPoint2:CGPointMake(551.172668,629.067810)]; + [complexShape addCurveToPoint:CGPointMake(530.000000,557.000000) controlPoint1:CGPointMake(525.270691,596.196289) controlPoint2:CGPointMake(531.172729,575.895203)]; + [complexShape addCurveToPoint:CGPointMake(539.000000,514.000000) controlPoint1:CGPointMake(528.119263,541.759460) controlPoint2:CGPointMake(537.465271,528.714905)]; + [complexShape addCurveToPoint:CGPointMake(557.000000,458.000000) controlPoint1:CGPointMake(546.810364,495.904419) controlPoint2:CGPointMake(549.387146,476.172821)]; + [complexShape addCurveToPoint:CGPointMake(577.000000,334.000000) controlPoint1:CGPointMake(570.620544,419.011993) controlPoint2:CGPointMake(584.251587,375.770599)]; + [complexShape addCurveToPoint:CGPointMake(552.000000,307.000000) controlPoint1:CGPointMake(572.811646,321.661407) controlPoint2:CGPointMake(563.442261,312.060883)]; + [complexShape addCurveToPoint:CGPointMake(505.000000,304.000000) controlPoint1:CGPointMake(537.517578,299.902161) controlPoint2:CGPointMake(520.555420,301.162994)]; + [complexShape addCurveToPoint:CGPointMake(417.000000,395.000000) controlPoint1:CGPointMake(471.423096,326.244507) controlPoint2:CGPointMake(426.598511,351.118713)]; + [complexShape addCurveToPoint:CGPointMake(412.000000,423.000000) controlPoint1:CGPointMake(413.398468,403.873810) controlPoint2:CGPointMake(411.718628,413.460388)]; + [complexShape addCurveToPoint:CGPointMake(418.000000,460.000000) controlPoint1:CGPointMake(412.695190,435.349243) controlPoint2:CGPointMake(409.621643,449.299377)]; + [complexShape addCurveToPoint:CGPointMake(445.000000,488.000000) controlPoint1:CGPointMake(425.380219,470.784973) controlPoint2:CGPointMake(435.315186,479.392761)]; + [complexShape addCurveToPoint:CGPointMake(469.000000,514.000000) controlPoint1:CGPointMake(453.446838,496.144928) controlPoint2:CGPointMake(463.737061,503.078857)]; + [complexShape addCurveToPoint:CGPointMake(476.000000,562.000000) controlPoint1:CGPointMake(477.951324,528.615112) controlPoint2:CGPointMake(475.631348,545.762817)]; + [complexShape addLineToPoint:CGPointMake(476.000000,562.000000)]; + [complexShape addLineToPoint:CGPointMake(384.000000,717.000000)]; + [complexShape addCurveToPoint:CGPointMake(382.000000,740.000000) controlPoint1:CGPointMake(381.948761,724.474304) controlPoint2:CGPointMake(381.285309,732.298340)]; + [complexShape addCurveToPoint:CGPointMake(394.000000,768.000000) controlPoint1:CGPointMake(373.999329,755.779053) controlPoint2:CGPointMake(424.795959,792.055847)]; + [complexShape addCurveToPoint:CGPointMake(466.000000,790.000000) controlPoint1:CGPointMake(413.033173,794.757141) controlPoint2:CGPointMake(440.293671,789.339783)]; + [complexShape addCurveToPoint:CGPointMake(509.000000,769.000000) controlPoint1:CGPointMake(479.958252,782.095642) controlPoint2:CGPointMake(496.425812,779.557007)]; + [complexShape addCurveToPoint:CGPointMake(570.000000,717.000000) controlPoint1:CGPointMake(536.317139,759.409058) controlPoint2:CGPointMake(549.962769,734.921936)]; + [complexShape addCurveToPoint:CGPointMake(632.000000,663.000000) controlPoint1:CGPointMake(586.772034,695.344360) controlPoint2:CGPointMake(605.504700,673.223633)]; + [complexShape addCurveToPoint:CGPointMake(679.000000,654.000000) controlPoint1:CGPointMake(645.724731,654.867432) controlPoint2:CGPointMake(663.048096,650.318481)]; + [complexShape addCurveToPoint:CGPointMake(696.000000,730.000000) controlPoint1:CGPointMake(702.263611,671.121704) controlPoint2:CGPointMake(698.817200,704.461426)]; + [complexShape addCurveToPoint:CGPointMake(685.000000,758.000000) controlPoint1:CGPointMake(693.381836,739.709473) controlPoint2:CGPointMake(689.682678,749.111511)]; + [complexShape addCurveToPoint:CGPointMake(639.000000,805.000000) controlPoint1:CGPointMake(674.516968,777.568542) controlPoint2:CGPointMake(664.349304,799.584106)]; + [complexShape addCurveToPoint:CGPointMake(605.000000,809.000000) controlPoint1:CGPointMake(627.873596,807.853577) controlPoint2:CGPointMake(616.339233,807.757568)]; + [complexShape addCurveToPoint:CGPointMake(559.000000,810.000000) controlPoint1:CGPointMake(589.680847,809.707275) controlPoint2:CGPointMake(574.264648,807.876892)]; + [complexShape addCurveToPoint:CGPointMake(491.000000,832.000000) controlPoint1:CGPointMake(535.253113,811.682922) controlPoint2:CGPointMake(510.867584,818.326538)]; + [complexShape addCurveToPoint:CGPointMake(483.000000,949.000000) controlPoint1:CGPointMake(452.488922,868.759766) controlPoint2:CGPointMake(506.715942,910.094971)]; + [complexShape addCurveToPoint:CGPointMake(438.000000,971.000000) controlPoint1:CGPointMake(471.816193,962.527100) controlPoint2:CGPointMake(455.094940,970.199341)]; + [complexShape addCurveToPoint:CGPointMake(381.000000,969.000000) controlPoint1:CGPointMake(419.703827,973.256775) controlPoint2:CGPointMake(398.264557,978.824951)]; + [complexShape addCurveToPoint:CGPointMake(332.000000,894.000000) controlPoint1:CGPointMake(336.834229,961.446167) controlPoint2:CGPointMake(348.029205,921.138672)]; + [complexShape addCurveToPoint:CGPointMake(201.000000,838.000000) controlPoint1:CGPointMake(307.704529,850.152588) controlPoint2:CGPointMake(249.883804,820.437744)]; + [complexShape addCurveToPoint:CGPointMake(167.000000,881.000000) controlPoint1:CGPointMake(182.977020,844.574768) controlPoint2:CGPointMake(166.990494,861.006348)]; + [complexShape addCurveToPoint:CGPointMake(175.000000,930.000000) controlPoint1:CGPointMake(165.412155,897.691711) controlPoint2:CGPointMake(168.718536,914.566101)]; + [complexShape addCurveToPoint:CGPointMake(169.000000,974.000000) controlPoint1:CGPointMake(175.880753,944.083801) controlPoint2:CGPointMake(184.246262,962.543579)]; + [complexShape addCurveToPoint:CGPointMake(112.000000,969.000000) controlPoint1:CGPointMake(153.879868,987.139404) controlPoint2:CGPointMake(121.948792,989.663391)]; + [complexShape addCurveToPoint:CGPointMake(99.000000,945.000000) controlPoint1:CGPointMake(105.050240,962.664062) controlPoint2:CGPointMake(100.558395,954.098572)]; + [complexShape addCurveToPoint:CGPointMake(97.000000,875.000000) controlPoint1:CGPointMake(95.831497,921.925537) controlPoint2:CGPointMake(94.644936,898.300354)]; + [complexShape addCurveToPoint:CGPointMake(132.000000,821.000000) controlPoint1:CGPointMake(107.640190,856.536194) controlPoint2:CGPointMake(116.238022,835.936340)]; + [complexShape addCurveToPoint:CGPointMake(184.000000,750.000000) controlPoint1:CGPointMake(155.798218,802.971130) controlPoint2:CGPointMake(173.768723,777.893677)]; + [complexShape addCurveToPoint:CGPointMake(187.000000,687.000000) controlPoint1:CGPointMake(185.273651,729.554688) controlPoint2:CGPointMake(197.039795,707.556824)]; + [complexShape addCurveToPoint:CGPointMake(154.000000,656.000000) controlPoint1:CGPointMake(180.328445,672.368408) controlPoint2:CGPointMake(164.994644,666.234436)]; + [complexShape addCurveToPoint:CGPointMake(196.000000,600.000000) controlPoint1:CGPointMake(149.363876,627.436218) controlPoint2:CGPointMake(180.133606,615.870422)]; + [complexShape addCurveToPoint:CGPointMake(268.000000,553.000000) controlPoint1:CGPointMake(219.965149,584.441223) controlPoint2:CGPointMake(243.722717,568.280640)]; + [complexShape addCurveToPoint:CGPointMake(295.000000,494.000000) controlPoint1:CGPointMake(285.621643,544.794250) controlPoint2:CGPointMake(308.387665,515.697510)]; + [complexShape addCurveToPoint:CGPointMake(223.000000,479.000000) controlPoint1:CGPointMake(274.950104,475.730682) controlPoint2:CGPointMake(247.686523,476.989319)]; + [complexShape addCurveToPoint:CGPointMake(115.000000,488.000000) controlPoint1:CGPointMake(190.196381,488.207886) controlPoint2:CGPointMake(149.008881,507.599640)]; + [complexShape addCurveToPoint:CGPointMake(92.000000,430.000000) controlPoint1:CGPointMake(91.723808,479.934326) controlPoint2:CGPointMake(81.161568,451.403015)]; + [complexShape addCurveToPoint:CGPointMake(185.000000,434.000000) controlPoint1:CGPointMake(119.158417,409.750031) controlPoint2:CGPointMake(157.642273,416.770874)]; + [complexShape addCurveToPoint:CGPointMake(298.000000,425.000000) controlPoint1:CGPointMake(219.467361,450.346191) controlPoint2:CGPointMake(268.697144,455.229706)]; + [complexShape addCurveToPoint:CGPointMake(339.000000,443.000000) controlPoint1:CGPointMake(312.625793,410.994751) controlPoint2:CGPointMake(331.307281,429.430817)]; + [complexShape addCurveToPoint:CGPointMake(349.000000,478.000000) controlPoint1:CGPointMake(344.453400,453.933380) controlPoint2:CGPointMake(347.832184,465.856812)]; + [complexShape addCurveToPoint:CGPointMake(344.000000,540.000000) controlPoint1:CGPointMake(351.007965,498.725006) controlPoint2:CGPointMake(352.137878,520.179993)]; + [complexShape addCurveToPoint:CGPointMake(316.000000,580.000000) controlPoint1:CGPointMake(339.491791,556.244446) controlPoint2:CGPointMake(328.167419,569.066284)]; + [complexShape addCurveToPoint:CGPointMake(273.000000,620.000000) controlPoint1:CGPointMake(299.305786,590.606079) controlPoint2:CGPointMake(286.016296,605.359131)]; + [complexShape addCurveToPoint:CGPointMake(219.000000,737.000000) controlPoint1:CGPointMake(253.199554,654.579529) controlPoint2:CGPointMake(210.386047,692.031433)]; + [complexShape addCurveToPoint:CGPointMake(249.000000,759.000000) controlPoint1:CGPointMake(225.442154,748.286621) controlPoint2:CGPointMake(236.628326,756.096069)]; + [complexShape addCurveToPoint:CGPointMake(334.000000,704.000000) controlPoint1:CGPointMake(285.551544,759.768677) controlPoint2:CGPointMake(315.180786,732.848572)]; + [complexShape addCurveToPoint:CGPointMake(352.000000,664.000000) controlPoint1:CGPointMake(342.386566,691.840271) controlPoint2:CGPointMake(348.465118,678.245239)]; + [complexShape addCurveToPoint:CGPointMake(363.000000,594.000000) controlPoint1:CGPointMake(356.415161,640.814575) controlPoint2:CGPointMake(362.213562,617.716248)]; + [complexShape addCurveToPoint:CGPointMake(375.000000,545.000000) controlPoint1:CGPointMake(364.278687,577.084534) controlPoint2:CGPointMake(369.613586,560.946472)]; + [complexShape addCurveToPoint:CGPointMake(370.000000,449.000000) controlPoint1:CGPointMake(385.057648,514.062866) controlPoint2:CGPointMake(387.877350,477.758911)]; + [complexShape addLineToPoint:CGPointMake(370.000000,449.000000)]; + [complexShape addLineToPoint:CGPointMake(349.000000,394.000000)]; + [complexShape addCurveToPoint:CGPointMake(317.000000,376.000000) controlPoint1:CGPointMake(342.462463,381.669800) controlPoint2:CGPointMake(329.632996,375.483673)]; + [complexShape addCurveToPoint:CGPointMake(288.000000,392.000000) controlPoint1:CGPointMake(302.854156,369.438843) controlPoint2:CGPointMake(296.932526,386.031342)]; + [complexShape closePath]; + [super setUp]; +} + +- (void)tearDown +{ + // Put teardown code here; it will be run once, after the last test case. + [super tearDown]; +} + +-(CGFloat) round:(CGFloat)val to:(int)digits{ + double factor = pow(10, digits); + return roundf(val * factor) / factor; +} + +-(BOOL) point:(CGPoint)p1 isNearTo:(CGPoint)p2{ + CGFloat xDiff = ABS(p2.x - p1.x); + CGFloat yDiff = ABS(p2.y - p1.y); + return xDiff < kIntersectionPointPrecision && yDiff < kIntersectionPointPrecision; +} + + +-(BOOL) check:(CGFloat) f1 isLessThan:(CGFloat)f2 within:(CGFloat)marginOfError{ + if(f1 <= (f2 * (1.0f+marginOfError))){ + return YES; + } + NSLog(@"float value %f is > %f", f1, f2); + return NO; +} + +-(BOOL) checkTanPoint:(CGFloat) f1 isLessThan:(CGFloat)f2{ + return [self check:f1 isLessThan:f2 within:.2]; +} + +@end diff --git a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m new file mode 100644 index 000000000..5cbd788e4 --- /dev/null +++ b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m @@ -0,0 +1,189 @@ +// +// PerformanceBezierClockwiseTests.m +// PerformanceBezier +// +// Created by Adam Wulf on 1/7/14. +// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. +// + +#import +#import "PerformanceBezierAbstractTest.h" + +@interface PerformanceBezierClockwiseTests : PerformanceBezierAbstractTest + +@end + +@implementation PerformanceBezierClockwiseTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here; it will be run once, before the first test case. +} + +- (void)tearDown +{ + // Put teardown code here; it will be run once, after the last test case. + [super tearDown]; +} + +- (void)testLinearCounterClockwise +{ + UIBezierPath* simplePath = [UIBezierPath bezierPath]; + [simplePath moveToPoint:CGPointMake(100, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 99)]; + + XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testLinearClockwise +{ + UIBezierPath* simplePath = [UIBezierPath bezierPath]; + [simplePath moveToPoint:CGPointMake(100, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 101)]; + + XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testLinearEmptyShape +{ + UIBezierPath* simplePath = [UIBezierPath bezierPath]; + [simplePath moveToPoint:CGPointMake(100, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 101)]; + [simplePath closePath]; + + XCTAssertTrue([simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testLinearEmptyShape2 +{ + UIBezierPath* simplePath = [UIBezierPath bezierPath]; + [simplePath moveToPoint:CGPointMake(100, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 100)]; + [simplePath addLineToPoint:CGPointMake(200, 99)]; + [simplePath closePath]; + + XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testSimpleClockwiseCurve +{ + UIBezierPath* simplePath = [UIBezierPath bezierPath]; + [simplePath moveToPoint:CGPointMake(100, 100)]; + [simplePath addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(100, 0) controlPoint2:CGPointMake(200, 0)]; + + XCTAssertTrue([simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testSimpleCounterClockwiseCurve +{ + UIBezierPath* simplePath = [UIBezierPath bezierPath]; + [simplePath moveToPoint:CGPointMake(100, 100)]; + [simplePath addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(100, 200) controlPoint2:CGPointMake(200, 200)]; + + XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testSimplePath +{ + UIBezierPath* simplePath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:10 startAngle:0 endAngle:M_PI clockwise:YES]; + + XCTAssertTrue([simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testSimplePath2 +{ + UIBezierPath* simplePath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:10 startAngle:0 endAngle:M_PI clockwise:NO]; + + XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); +} + +- (void)testComplexPath +{ + XCTAssertTrue([self.complexShape isClockwise], @"clockwise is correct"); +} + +- (void)testReversedComplexPath +{ + XCTAssertTrue(![[self.complexShape bezierPathByReversingPath] isClockwise], @"clockwise is correct"); +} + +- (void)testFirstAndLastPointRect { + // This is an example of a functional test case. + + UIBezierPath* path1 = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 20, 20)]; + XCTAssertEqual([path1 elementCount], 5, "element count is correct"); + XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].y, (CGFloat) 10, "element count is correct"); +} + +- (void)testFirstAndLastPointLine { + // This is an example of a functional test case. + + UIBezierPath* path1 = [UIBezierPath bezierPath]; + [path1 moveToPoint:CGPointMake(10, 10)]; + [path1 addLineToPoint:CGPointMake(30, 30)]; + XCTAssertEqual([path1 elementCount], 2, "element count is correct"); + XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct"); + XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct"); +} + + +- (void)testFirstAndLastPointCurve { + // This is an example of a functional test case. + + UIBezierPath* path1 = [UIBezierPath bezierPath]; + [path1 moveToPoint:CGPointMake(10, 10)]; + [path1 addCurveToPoint:CGPointMake(30, 30) controlPoint1:CGPointMake(15, 15) controlPoint2:CGPointMake(25, 25)]; + XCTAssertEqual([path1 elementCount], 2, "element count is correct"); + XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct"); + XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct"); +} + +- (void)testFirstAndLastPointQuadCurve { + // This is an example of a functional test case. + + UIBezierPath* path1 = [UIBezierPath bezierPath]; + [path1 moveToPoint:CGPointMake(10, 10)]; + [path1 addQuadCurveToPoint:CGPointMake(30, 30) controlPoint:CGPointMake(20, 20)]; + XCTAssertEqual([path1 elementCount], 2, "element count is correct"); + XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct"); + XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct"); +} + +- (void)testFirstAndLastPointMoveTo { + // This is an example of a functional test case. + + UIBezierPath* path1 = [UIBezierPath bezierPath]; + [path1 moveToPoint:CGPointMake(10, 10)]; + XCTAssertEqual([path1 elementCount], 1, "element count is correct"); + XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].y, (CGFloat) 10, "element count is correct"); +} + +- (void)testFirstAndLastPointMoveTo2 { + // This is an example of a functional test case. + + UIBezierPath* path1 = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 20, 20)]; + [path1 moveToPoint:CGPointMake(50, 50)]; + XCTAssertEqual([path1 elementCount], 6, "element count is correct"); + XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); + XCTAssertEqual([path1 lastPoint].x, (CGFloat) 50, "element count is correct"); + XCTAssertEqual([path1 lastPoint].y, (CGFloat) 50, "element count is correct"); +} + +@end diff --git a/ios/PerformanceBezier/README.md b/ios/PerformanceBezier/README.md new file mode 100644 index 000000000..efebe748a --- /dev/null +++ b/ios/PerformanceBezier/README.md @@ -0,0 +1,67 @@ +iOS UIBezierPath Performance +===== + +This code dramatically improves performance for common UIBezierPath operations, and it also +brings UIBezierPath API closer to its NSBezierPath counterpart. For full background of this +repo, checkout [the blogpost explaining what this framework does](http://blog.getlooseleaf.com/post/110511009139/improving-uibezierpath-performance-and-api). + +This code was originally part of [Loose Leaf](https://getlooseleaf.com/). Additional components and +libraries from the app [have also been open sourced](https://getlooseleaf.com/opensource/). + +## What is this? + +This framework adds caching into every UIBezierPath so that common operations can +be performed in constant time. It also adds some missing NSBezierPath methods to the +UIBezierPath class. + +After linking this framework into your project, all Bezier paths will automatically be upgraded +to use this new caching. No custom UIBezierPath allocation or initialization is required. + +For example, by default there is no O(1) way to retrieve elements from a UIBezierPath. In order to +retrieve the first point of the curve, you must CGPathApply() and interate over the entire path +to retrieve that single point. This framework changes that. For many algorithms, this can +dramatically affect performance. + +## Are you using PerformanceBezier? + +Let me know! I'd love to know where PerformanceBezier is using and how it's affecting your apps. Ping me +at [@adamwulf](https://twitter.com/adamwulf)! + +Also - If you like PerformanceBezier, then you'll _love_ [ClippingBezier](https://github.com/adamwulf/ClippingBezier) - an easy way to find intersecting points, lines, and shapes between two UIBezierPaths. + +## Documentation + +View the header files for full documentation. + +## Building the framework + +This library will generate a proper static framework bundle that can be used in any iOS7+ project. + +## Including in your project + +1. Link against the built framework. +2. Add "-ObjC++ -lstdc++" to the Other Linker Flags in the project's Settings +3. #import <PerformanceBezier/PerformanceBezier.h> + +## JRSwizzle + +This framework includes and uses the [JRSwizzle](https://github.com/rentzsch/jrswizzle) library, which is +licensed under the MIT license. + +## License + +Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 United States License. + +For attribution, please include: + +1. Mention original author "Adam Wulf for Loose Leaf app" +2. Link to https://getlooseleaf.com/opensource/ +3. Link to https://github.com/adamwulf/PerformanceBezier + + + +## Support this framework + +This framework is created by Adam Wulf ([@adamwulf](https://twitter.com/adamwulf)) as a part of the [Loose Leaf app](https://getlooseleaf.com). + +[Buy the app](https://itunes.apple.com/us/app/loose-leaf/id625659452?mt=8&uo=4&at=10lNUI&ct=github) to show your support! :) diff --git a/ios/PerformanceBezier/SubdivideLicense b/ios/PerformanceBezier/SubdivideLicense new file mode 100644 index 000000000..119a1a69c --- /dev/null +++ b/ios/PerformanceBezier/SubdivideLicense @@ -0,0 +1,32 @@ +/* +* Bezier path utility category (trimming) +* +* (c) 2004 Alastair J. Houghton +* All Rights Reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* 3. The name of the author of this software may not be used to endorse +* or promote products derived from the software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO +* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ \ No newline at end of file diff --git a/ios/QuartzBookPack/Bezier/Bezier.h b/ios/QuartzBookPack/Bezier/Bezier.h new file mode 100644 index 000000000..a418bf3a6 --- /dev/null +++ b/ios/QuartzBookPack/Bezier/Bezier.h @@ -0,0 +1,17 @@ +/* + + Erica Sadun, http://ericasadun.com + + BEZIER PACK + + */ + +#import +#import +#import + +#import "BaseGeometry.h" +#import "BezierUtils.h" +#import "BezierElement.h" +#import "BezierFunctions.h" +#import "UIBezierPath+Elements.h" diff --git a/ios/QuartzBookPack/Bezier/BezierElement.h b/ios/QuartzBookPack/Bezier/BezierElement.h new file mode 100644 index 000000000..315a156cf --- /dev/null +++ b/ios/QuartzBookPack/Bezier/BezierElement.h @@ -0,0 +1,34 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + + +#import +#import + +// Block for transforming points +typedef CGPoint (^PathBlock)(CGPoint point); + +@interface BezierElement : NSObject + +// Element storage +@property (nonatomic, assign) CGPathElementType elementType; +@property (nonatomic, assign) CGPoint point; +@property (nonatomic, assign) CGPoint controlPoint1; +@property (nonatomic, assign) CGPoint controlPoint2; + +// Instance creation ++ (instancetype) elementWithPathElement: (CGPathElement) element; + +// Applying transformations +- (BezierElement *) elementByApplyingBlock: (PathBlock) block; + +// Adding to path +- (void) addToPath: (UIBezierPath *) path; + +// Readable forms +@property (nonatomic, readonly) NSString *stringValue; +- (void) showTheCode; +@end; diff --git a/ios/QuartzBookPack/Bezier/BezierElement.m b/ios/QuartzBookPack/Bezier/BezierElement.m new file mode 100644 index 000000000..057a2bea7 --- /dev/null +++ b/ios/QuartzBookPack/Bezier/BezierElement.m @@ -0,0 +1,163 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + + +#import "Bezier.h" + +#pragma mark - Bezier Element - + +@implementation BezierElement +- (instancetype) init +{ + self = [super init]; + if (self) + { + _elementType = kCGPathElementMoveToPoint; + _point = NULLPOINT; + _controlPoint1 = NULLPOINT; + _controlPoint2 = NULLPOINT; + } + return self; +} + ++ (instancetype) elementWithPathElement: (CGPathElement) element +{ + BezierElement *newElement = [[self alloc] init]; + newElement.elementType = element.type; + + switch (newElement.elementType) + { + case kCGPathElementCloseSubpath: + break; + case kCGPathElementMoveToPoint: + case kCGPathElementAddLineToPoint: + { + newElement.point = element.points[0]; + break; + } + case kCGPathElementAddQuadCurveToPoint: + { + newElement.point = element.points[1]; + newElement.controlPoint1 = element.points[0]; + break; + } + case kCGPathElementAddCurveToPoint: + { + newElement.point = element.points[2]; + newElement.controlPoint1 = element.points[0]; + newElement.controlPoint2 = element.points[1]; + break; + } + default: + break; + } + + return newElement; +} + +- (instancetype) copyWithZone: (NSZone *) zone +{ + BezierElement *theCopy = [[[self class] allocWithZone:zone] init]; + if (theCopy) + { + theCopy.elementType = _elementType; + theCopy.point = _point; + theCopy.controlPoint1 = _controlPoint1; + theCopy.controlPoint2 = _controlPoint2; + } + return theCopy; +} + +#pragma mark - Path + +- (BezierElement *) elementByApplyingBlock: (PathBlock) block +{ + BezierElement *output = [self copy]; + if (!block) + return output; + + if (!POINT_IS_NULL(output.point)) + output.point = block(output.point); + if (!POINT_IS_NULL(output.controlPoint1)) + output.controlPoint1 = block(output.controlPoint1); + if (!POINT_IS_NULL(output.controlPoint2)) + output.controlPoint2 = block(output.controlPoint2); + return output; +} + +- (void) addToPath: (UIBezierPath *) path +{ + switch (self.elementType) + { + case kCGPathElementCloseSubpath: + [path closePath]; + break; + case kCGPathElementMoveToPoint: + [path moveToPoint:self.point]; + break; + case kCGPathElementAddLineToPoint: + [path addLineToPoint:self.point]; + break; + case kCGPathElementAddQuadCurveToPoint: + [path addQuadCurveToPoint:self.point controlPoint:self.controlPoint1]; + break; + case kCGPathElementAddCurveToPoint: + [path addCurveToPoint:self.point controlPoint1:self.controlPoint1 controlPoint2:self.controlPoint2]; + break; + default: + break; + } +} + +#pragma mark - Strings + +- (NSString *) stringValue +{ + switch (self.elementType) + { + case kCGPathElementCloseSubpath: + return @"Close Path"; + case kCGPathElementMoveToPoint: + return [NSString stringWithFormat:@"Move to point %@", POINTSTRING(self.point)]; + case kCGPathElementAddLineToPoint: + return [NSString stringWithFormat:@"Add line to point %@", POINTSTRING(self.point)]; + case kCGPathElementAddQuadCurveToPoint: + return [NSString stringWithFormat:@"Add quad curve to point %@ with control point %@", POINTSTRING(self.point), POINTSTRING(self.controlPoint1)]; + case kCGPathElementAddCurveToPoint: + return [NSString stringWithFormat:@"Add curve to point %@ with control points %@ and %@", POINTSTRING(self.point), POINTSTRING(self.controlPoint1), POINTSTRING(self.controlPoint2)]; + } + return nil; +} + +- (void) showTheCode +{ + switch (self.elementType) + { + case kCGPathElementCloseSubpath: + printf(" [path closePath];\n\n"); + break; + case kCGPathElementMoveToPoint: + printf(" [path moveToPoint:CGPointMake(%f, %f)];\n", + self.point.x, self.point.y); + break; + case kCGPathElementAddLineToPoint: + printf(" [path addLineToPoint:CGPointMake(%f, %f)];\n", + self.point.x, self.point.y); + break; + case kCGPathElementAddQuadCurveToPoint: + printf(" [path addQuadCurveToPoint:CGPointMake(%f, %f) controlPoint:CGPointMake(%f, %f)];\n", + self.point.x, self.point.y, self.controlPoint1.x, self.controlPoint1.y); + break; + case kCGPathElementAddCurveToPoint: + printf(" [path addCurveToPoint:CGPointMake(%f, %f) controlPoint1:CGPointMake(%f, %f) controlPoint2:CGPointMake(%f, %f)];\n", + self.point.x, self.point.y, self.controlPoint1.x, self.controlPoint1.y, self.controlPoint2.x, self.controlPoint2.y); + break; + default: + break; + } +} +@end + diff --git a/ios/QuartzBookPack/Bezier/BezierFunctions.h b/ios/QuartzBookPack/Bezier/BezierFunctions.h new file mode 100644 index 000000000..74053634f --- /dev/null +++ b/ios/QuartzBookPack/Bezier/BezierFunctions.h @@ -0,0 +1,38 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import "BezierElement.h" + +#define NUMBER_OF_BEZIER_SAMPLES 6 + +typedef CGFloat (^InterpolationBlock)(CGFloat percent); + +// Return Bezier Value +float CubicBezier(float t, float start, float c1, float c2, float end); +float QuadBezier(float t, float start, float c1, float end); + +// Return Bezier Point +CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end); +CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end); + +// Calculate Curve Length +float CubicBezierLength(CGPoint start, CGPoint c1, CGPoint c2, CGPoint end); +float QuadBezierLength(CGPoint start, CGPoint c1, CGPoint end); + +// Element Distance +CGFloat ElementDistanceFromPoint(BezierElement *element, CGPoint point, CGPoint startPoint); + +// Linear Interpolation +CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope); + +// Interpolate along element +CGPoint InterpolatePointFromElement(BezierElement *element, CGPoint point, CGPoint startPoint, CGFloat percent, CGPoint *slope); + +// Ease +CGFloat EaseIn(CGFloat currentTime, int factor); +CGFloat EaseOut(CGFloat currentTime, int factor); +CGFloat EaseInOut(CGFloat currentTime, int factor); diff --git a/ios/QuartzBookPack/Bezier/BezierFunctions.m b/ios/QuartzBookPack/Bezier/BezierFunctions.m new file mode 100644 index 000000000..6003e404f --- /dev/null +++ b/ios/QuartzBookPack/Bezier/BezierFunctions.m @@ -0,0 +1,205 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "Bezier.h" + +#pragma mark - Bezier functions +float CubicBezier(float t, float start, float c1, float c2, float end) +{ + CGFloat t_ = (1.0 - t); + CGFloat tt_ = t_ * t_; + CGFloat ttt_ = t_ * t_ * t_; + CGFloat tt = t * t; + CGFloat ttt = t * t * t; + + return start * ttt_ + + 3.0 * c1 * tt_ * t + + 3.0 * c2 * t_ * tt + + end * ttt; +} + +float QuadBezier(float t, float start, float c1, float end) +{ + CGFloat t_ = (1.0 - t); + CGFloat tt_ = t_ * t_; + CGFloat tt = t * t; + + return start * tt_ + + 2.0 * c1 * t_ * t + + end * tt; +} + +CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end) +{ + CGPoint result; + result.x = CubicBezier(t, start.x, c1.x, c2.x, end.x); + result.y = CubicBezier(t, start.y, c1.y, c2.y, end.y); + return result; +} + +CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end) +{ + CGPoint result; + result.x = QuadBezier(t, start.x, c1.x, end.x); + result.y = QuadBezier(t, start.y, c1.y, end.y); + return result; +} + +float CubicBezierLength(CGPoint start, CGPoint c1, CGPoint c2, CGPoint end) +{ + int steps = NUMBER_OF_BEZIER_SAMPLES; + CGPoint current; + CGPoint previous; + float length = 0.0; + + for (int i = 0; i <= steps; i++) + { + float t = (float) i / (float) steps; + current = CubicBezierPoint(t, start, c1, c2, end); + if (i > 0) + length += PointDistanceFromPoint(current, previous); + previous = current; + } + + return length; +} + +float QuadBezierLength(CGPoint start, CGPoint c1, CGPoint end) +{ + int steps = NUMBER_OF_BEZIER_SAMPLES; + CGPoint current; + CGPoint previous; + float length = 0.0; + + for (int i = 0; i <= steps; i++) + { + float t = (float) i / (float) steps; + current = QuadBezierPoint(t, start, c1, end); + if (i > 0) + length += PointDistanceFromPoint(current, previous); + previous = current; + } + + return length; +} + + +#pragma mark - Point Percents +#define USE_CURVE_TWEAK 0 +#if USE_CURVE_TWEAK +#define CURVETWEAK 0.8 +#else +#define CURVETWEAK 1.0 +#endif + +CGFloat ElementDistanceFromPoint(BezierElement *element, CGPoint point, CGPoint startPoint) +{ + CGFloat distance = 0.0f; + switch (element.elementType) + { + case kCGPathElementMoveToPoint: + return 0.0f; + case kCGPathElementCloseSubpath: + return PointDistanceFromPoint(point, startPoint); + case kCGPathElementAddLineToPoint: + return PointDistanceFromPoint(point, element.point); + case kCGPathElementAddCurveToPoint: + return CubicBezierLength(point, element.controlPoint1, element.controlPoint2, element.point) * CURVETWEAK; + case kCGPathElementAddQuadCurveToPoint: + return QuadBezierLength(point, element.controlPoint1, element.point) * CURVETWEAK; + } + + return distance; +} + +// Centralize for both close subpath and add line to point +CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope) +{ + CGFloat dx = p2.x - p1.x; + CGFloat dy = p2.y - p1.y; + + if (slope) + *slope = CGPointMake(dx, dy); + + CGFloat px = p1.x + dx * percent; + CGFloat py = p1.y + dy * percent; + + return CGPointMake(px, py); +} + +// Interpolate along element +CGPoint InterpolatePointFromElement(BezierElement *element, CGPoint point, CGPoint startPoint, CGFloat percent, CGPoint *slope) +{ + switch (element.elementType) + { + case kCGPathElementMoveToPoint: + { + // No distance + if (slope) + *slope = CGPointMake(INFINITY, INFINITY); + return point; + } + + case kCGPathElementCloseSubpath: + { + // from self.point to firstPoint + CGPoint p = InterpolateLineSegment(point, startPoint, percent, slope); + return p; + } + + case kCGPathElementAddLineToPoint: + { + // from point to self.point + CGPoint p = InterpolateLineSegment(point, element.point, percent, slope); + return p; + } + + case kCGPathElementAddQuadCurveToPoint: + { + // from point to self.point + CGPoint p = QuadBezierPoint(percent, point, element.controlPoint1, element.point); + CGFloat dx = p.x - QuadBezier(percent * 0.9, point.x, element.controlPoint1.x, element.point.x); + CGFloat dy = p.y - QuadBezier(percent * 0.9, point.y, element.controlPoint1.y, element.point.y); + if (slope) + *slope = CGPointMake(dx, dy); + return p; + } + + case kCGPathElementAddCurveToPoint: + { + // from point to self.point + CGPoint p = CubicBezierPoint(percent, point, element.controlPoint1, element.controlPoint2, element.point); + CGFloat dx = p.x - CubicBezier(percent * 0.9, point.x, element.controlPoint1.x, element.controlPoint2.x, element.point.x); + CGFloat dy = p.y - CubicBezier(percent * 0.9, point.y, element.controlPoint1.y, element.controlPoint2.y, element.point.y); + if (slope) + *slope = CGPointMake(dx, dy); + return p; + } + } + + return NULLPOINT; +} + +CGFloat EaseIn(CGFloat currentTime, int factor) +{ + return powf(currentTime, factor); +} + +CGFloat EaseOut(CGFloat currentTime, int factor) +{ + return 1 - powf((1 - currentTime), factor); +} + +CGFloat EaseInOut(CGFloat currentTime, int factor) +{ + currentTime = currentTime * 2.0; + if (currentTime < 1) + return (0.5 * pow(currentTime, factor)); + currentTime -= 2.0; + if (factor % 2) + return 0.5 * (pow(currentTime, factor) + 2.0); + return 0.5 * (2.0 - pow(currentTime, factor)); +} \ No newline at end of file diff --git a/ios/QuartzBookPack/Bezier/BezierUtils.h b/ios/QuartzBookPack/Bezier/BezierUtils.h new file mode 100644 index 000000000..84c1794a6 --- /dev/null +++ b/ios/QuartzBookPack/Bezier/BezierUtils.h @@ -0,0 +1,87 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import "BaseGeometry.h" + +CGRect PathBoundingBox(UIBezierPath *path); +CGRect PathBoundingBoxWithLineWidth(UIBezierPath *path); +CGPoint PathBoundingCenter(UIBezierPath *path); +CGPoint PathCenter(UIBezierPath *path); + +// Transformations +void ApplyCenteredPathTransform(UIBezierPath *path, CGAffineTransform transform); +UIBezierPath *PathByApplyingTransform(UIBezierPath *path, CGAffineTransform transform); + +// Utility +void RotatePath(UIBezierPath *path, CGFloat theta); +void ScalePath(UIBezierPath *path, CGFloat sx, CGFloat sy); +void OffsetPath(UIBezierPath *path, CGSize offset); +void MovePathToPoint(UIBezierPath *path, CGPoint point); +void MovePathCenterToPoint(UIBezierPath *path, CGPoint point); +void MirrorPathHorizontally(UIBezierPath *path); +void MirrorPathVertically(UIBezierPath *path); + +// Fitting +void FitPathToRect(UIBezierPath *path, CGRect rect); +void AdjustPathToRect(UIBezierPath *path, CGRect destRect); + +// Path Attributes +void CopyBezierState(UIBezierPath *source, UIBezierPath *destination); +void CopyBezierDashes(UIBezierPath *source, UIBezierPath *destination); +void AddDashesToPath(UIBezierPath *path); + +// String to Path +UIBezierPath *BezierPathFromString(NSString *string, UIFont *font); +UIBezierPath *BezierPathFromStringWithFontFace(NSString *string, NSString *fontFace); + +// Draw Text in Path +void DrawAttributedStringInBezierPath(UIBezierPath *path, NSAttributedString *attributedString); +void DrawAttributedStringInBezierSubpaths(UIBezierPath *path, NSAttributedString *attributedString); + +// N-Gons +UIBezierPath *BezierPolygon(NSUInteger numberOfSides); +UIBezierPath *BezierInflectedShape(NSUInteger numberOfInflections, CGFloat percentInflection); +UIBezierPath *BezierStarShape(NSUInteger numberOfInflections, CGFloat percentInflection); + +// Misc +void ClipToRect(CGRect rect); +void FillRect(CGRect rect, UIColor *color); +void ShowPathProgression(UIBezierPath *path, CGFloat maxPercent); + +// Effects +void SetShadow(UIColor *color, CGSize size, CGFloat blur); +void DrawShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur); +void DrawInnerShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur); +void EmbossPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat blur); +void BevelPath(UIBezierPath *p, UIColor *color, CGFloat r, CGFloat theta); +void InnerBevel(UIBezierPath *p, UIColor *color, CGFloat r, CGFloat theta); +void ExtrudePath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat angle); + +@interface UIBezierPath (HandyUtilities) +@property (nonatomic, readonly) CGPoint center; +@property (nonatomic, readonly) CGRect computedBounds; +@property (nonatomic, readonly) CGRect computedBoundsWithLineWidth; + +// Stroke/Fill +- (void) stroke: (CGFloat) width; +- (void) stroke: (CGFloat) width color: (UIColor *) color; +- (void) strokeInside: (CGFloat) width; +- (void) strokeInside: (CGFloat) width color: (UIColor *) color; +- (void) fill: (UIColor *) fillColor; +- (void) fill: (UIColor *) fillColor withMode: (CGBlendMode) blendMode; +- (void) fillWithNoise: (UIColor *) fillColor; +- (void) addDashes; +- (void) addDashes: (NSArray *) pattern; +- (void) applyPathPropertiesToContext; + +// Clipping +- (void) clipToPath; // I hate addClip +- (void) clipToStroke: (NSUInteger) width; + +// Util +- (UIBezierPath *) safeCopy; +@end diff --git a/ios/QuartzBookPack/Bezier/BezierUtils.m b/ios/QuartzBookPack/Bezier/BezierUtils.m new file mode 100644 index 000000000..018a1c129 --- /dev/null +++ b/ios/QuartzBookPack/Bezier/BezierUtils.m @@ -0,0 +1,709 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import "BezierUtils.h" +#import "Utility.h" + +#pragma mark - Bounds +CGRect PathBoundingBox(UIBezierPath *path) +{ + return CGPathGetPathBoundingBox(path.CGPath); +} + +CGRect PathBoundingBoxWithLineWidth(UIBezierPath *path) +{ + CGRect bounds = PathBoundingBox(path); + return CGRectInset(bounds, -path.lineWidth / 2.0f, -path.lineWidth / 2.0f); +} + +CGPoint PathBoundingCenter(UIBezierPath *path) +{ + return RectGetCenter(PathBoundingBox(path)); +} + +CGPoint PathCenter(UIBezierPath *path) +{ + return RectGetCenter(path.bounds); +} + +#pragma mark - Misc +void ClipToRect(CGRect rect) +{ + [[UIBezierPath bezierPathWithRect:rect] addClip]; +} + +void FillRect(CGRect rect, UIColor *color) +{ + [[UIBezierPath bezierPathWithRect:rect] fill:color]; +} + +void ShowPathProgression(UIBezierPath *path, CGFloat maxPercent) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGFloat maximumPercent = fmax(fmin(maxPercent, 1.0f), 0.0f); + PushDraw(^{ + CGFloat distance = path.pathLength; + int samples = distance / 6; + float dLevel = 0.75 / (CGFloat) samples; + + UIBezierPath *marker; + for (int i = 0; i <= samples * maximumPercent; i++) + { + CGFloat percent = (CGFloat) i / (CGFloat) samples; + CGPoint point = [path pointAtPercent:percent withSlope:NULL]; + UIColor *color = [UIColor colorWithWhite:i * dLevel alpha:1]; + + CGRect r = RectAroundCenter(point, CGSizeMake(2, 2)); + marker = [UIBezierPath bezierPathWithOvalInRect:r]; + [marker fill:color]; + } + }); +} + +#pragma mark - Transform +void ApplyCenteredPathTransform(UIBezierPath *path, CGAffineTransform transform) +{ + CGPoint center = PathBoundingCenter(path); + CGAffineTransform t = CGAffineTransformIdentity; + t = CGAffineTransformTranslate(t, center.x, center.y); + t = CGAffineTransformConcat(transform, t); + t = CGAffineTransformTranslate(t, -center.x, -center.y); + [path applyTransform:t]; +} + +UIBezierPath *PathByApplyingTransform(UIBezierPath *path, CGAffineTransform transform) +{ + UIBezierPath *copy = [path copy]; + ApplyCenteredPathTransform(copy, transform); + return copy; +} + +void RotatePath(UIBezierPath *path, CGFloat theta) +{ + CGAffineTransform t = CGAffineTransformMakeRotation(theta); + ApplyCenteredPathTransform(path, t); +} + +void ScalePath(UIBezierPath *path, CGFloat sx, CGFloat sy) +{ + CGAffineTransform t = CGAffineTransformMakeScale(sx, sy); + ApplyCenteredPathTransform(path, t); +} + +void OffsetPath(UIBezierPath *path, CGSize offset) +{ + CGAffineTransform t = CGAffineTransformMakeTranslation(offset.width, offset.height); + ApplyCenteredPathTransform(path, t); +} + +void MovePathToPoint(UIBezierPath *path, CGPoint destPoint) +{ + CGRect bounds = PathBoundingBox(path); + CGPoint p1 = bounds.origin; + CGPoint p2 = destPoint; + CGSize vector = CGSizeMake(p2.x - p1.x, p2.y - p1.y); + OffsetPath(path, vector); +} + +void MovePathCenterToPoint(UIBezierPath *path, CGPoint destPoint) +{ + CGRect bounds = PathBoundingBox(path); + CGPoint p1 = bounds.origin; + CGPoint p2 = destPoint; + CGSize vector = CGSizeMake(p2.x - p1.x, p2.y - p1.y); + vector.width -= bounds.size.width / 2.0f; + vector.height -= bounds.size.height / 2.0f; + OffsetPath(path, vector); +} + +void MirrorPathHorizontally(UIBezierPath *path) +{ + CGAffineTransform t = CGAffineTransformMakeScale(-1, 1); + ApplyCenteredPathTransform(path, t); +} + +void MirrorPathVertically(UIBezierPath *path) +{ + CGAffineTransform t = CGAffineTransformMakeScale(1, -1); + ApplyCenteredPathTransform(path, t); +} + +void FitPathToRect(UIBezierPath *path, CGRect destRect) +{ + CGRect bounds = PathBoundingBox(path); + CGRect fitRect = RectByFittingRect(bounds, destRect); + CGFloat scale = AspectScaleFit(bounds.size, destRect); + + CGPoint newCenter = RectGetCenter(fitRect); + MovePathCenterToPoint(path, newCenter); + ScalePath(path, scale, scale); +} + +void AdjustPathToRect(UIBezierPath *path, CGRect destRect) +{ + CGRect bounds = PathBoundingBox(path); + CGFloat scaleX = destRect.size.width / bounds.size.width; + CGFloat scaleY = destRect.size.height / bounds.size.height; + + CGPoint newCenter = RectGetCenter(destRect); + MovePathCenterToPoint(path, newCenter); + ScalePath(path, scaleX, scaleY); +} + +#pragma mark - Path Attributes +void AddDashesToPath(UIBezierPath *path) +{ + CGFloat dashes[] = {6, 2}; + [path setLineDash:dashes count:2 phase:0]; +} + +void CopyBezierDashes(UIBezierPath *source, UIBezierPath *destination) +{ + NSInteger count; + [source getLineDash:NULL count:&count phase:NULL]; + + CGFloat phase; + CGFloat *pattern = malloc(count * sizeof(CGFloat)); + [source getLineDash:pattern count:&count phase:&phase]; + [destination setLineDash:pattern count:count phase:phase]; + free(pattern); +} + +void CopyBezierState(UIBezierPath *source, UIBezierPath *destination) +{ + destination.lineWidth = source.lineWidth; + destination.lineCapStyle = source.lineCapStyle; + destination.lineJoinStyle = source.lineJoinStyle; + destination.miterLimit = source.miterLimit; + destination.flatness = source.flatness; + destination.usesEvenOddFillRule = source.usesEvenOddFillRule; + CopyBezierDashes(source, destination); +} + +#pragma mark - Text +UIBezierPath *BezierPathFromString(NSString *string, UIFont *font) +{ + // Initialize path + UIBezierPath *path = [UIBezierPath bezierPath]; + if (!string.length) return path; + + // Create font ref + CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); + if (fontRef == NULL) + { + NSLog(@"Error retrieving CTFontRef from UIFont"); + return nil; + } + + // Create glyphs + CGGlyph *glyphs = malloc(sizeof(CGGlyph) * string.length); + const unichar *chars = (const unichar *)[string cStringUsingEncoding:NSUnicodeStringEncoding]; + BOOL success = CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, string.length); + if (!success) + { + NSLog(@"Error retrieving string glyphs"); + CFRelease(fontRef); + free(glyphs); + return nil; + } + + // Draw each char into path + for (int i = 0; i < string.length; i++) + { + CGGlyph glyph = glyphs[i]; + CGPathRef pathRef = CTFontCreatePathForGlyph(fontRef, glyph, NULL); + [path appendPath:[UIBezierPath bezierPathWithCGPath:pathRef]]; + CGPathRelease(pathRef); + CGSize size = [[string substringWithRange:NSMakeRange(i, 1)] sizeWithAttributes:@{NSFontAttributeName:font}]; + OffsetPath(path, CGSizeMake(-size.width, 0)); + } + + // Clean up + free(glyphs); + CFRelease(fontRef); + + // Math + MirrorPathVertically(path); + return path; +} + +UIBezierPath *BezierPathFromStringWithFontFace(NSString *string, NSString *fontFace) +{ + UIFont *font = [UIFont fontWithName:fontFace size:16]; + if (!font) + font = [UIFont systemFontOfSize:16]; + return BezierPathFromString(string, font); +} + +// Listing 8-1 +void MirrorPathVerticallyInContext(UIBezierPath *path) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGSize size = GetUIKitContextSize(); + CGRect contextRect = SizeMakeRect(size); + CGPoint center = RectGetCenter(contextRect); + + CGAffineTransform t = CGAffineTransformIdentity; + t = CGAffineTransformTranslate(t, center.x, center.y); + t = CGAffineTransformScale(t, 1, -1); + t = CGAffineTransformTranslate(t, -center.x, -center.y); + [path applyTransform:t]; +} + +void DrawAttributedStringIntoSubpath(UIBezierPath *path, NSAttributedString *attributedString, NSAttributedString **remainder) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + UIBezierPath *copy = [path safeCopy]; + MirrorPathVerticallyInContext(copy); + + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attributedString); + CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedString.length), copy.CGPath, NULL); + + if (remainder) + { + CFRange range = CTFrameGetVisibleStringRange(theFrame); + NSInteger startLocation = range.location + range.length; + NSInteger extent = attributedString.length - startLocation; + NSAttributedString *substring = [attributedString attributedSubstringFromRange:NSMakeRange(startLocation, extent)]; + *remainder = substring; + } + + PushDraw(^{ + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + FlipContextVertically(GetUIKitContextSize()); + CTFrameDraw(theFrame, UIGraphicsGetCurrentContext()); + }); + + CFRelease(theFrame); + CFRelease(framesetter); +} + +void DrawAttributedStringInBezierPath(UIBezierPath *path, NSAttributedString *attributedString) +{ + DrawAttributedStringIntoSubpath(path, attributedString, nil); +} + +void DrawAttributedStringInBezierSubpaths(UIBezierPath *path, NSAttributedString *attributedString) +{ + NSAttributedString *string; + NSAttributedString *remainder = attributedString; + + for (UIBezierPath *subpath in path.subpaths) + { + string = remainder; + DrawAttributedStringIntoSubpath(subpath, string, &remainder); + if (remainder.length == 0) return; + } +} + +#pragma mark - Polygon Fun +UIBezierPath *BezierPolygon(NSUInteger numberOfSides) +{ + if (numberOfSides < 3) + { + NSLog(@"Error: Please supply at least 3 sides"); + return nil; + } + + CGRect destinationRect = CGRectMake(0, 0, 1, 1); + + UIBezierPath *path = [UIBezierPath bezierPath]; + CGPoint center = RectGetCenter(destinationRect); + CGFloat r = 0.5f; // radius + + BOOL firstPoint = YES; + for (int i = 0; i < (numberOfSides - 1); i++) + { + CGFloat theta = M_PI + i * TWO_PI / numberOfSides; + CGFloat dTheta = TWO_PI / numberOfSides; + + CGPoint p; + if (firstPoint) + { + p.x = center.x + r * sin(theta); + p.y = center.y + r * cos(theta); + [path moveToPoint:p]; + firstPoint = NO; + } + + p.x = center.x + r * sin(theta + dTheta); + p.y = center.y + r * cos(theta + dTheta); + [path addLineToPoint:p]; + } + + [path closePath]; + + return path; +} + +UIBezierPath *BezierInflectedShape(NSUInteger numberOfInflections, CGFloat percentInflection) +{ + if (numberOfInflections < 3) + { + NSLog(@"Error: Please supply at least 3 inflections"); + return nil; + } + + UIBezierPath *path = [UIBezierPath bezierPath]; + CGRect destinationRect = CGRectMake(0, 0, 1, 1); + CGPoint center = RectGetCenter(destinationRect); + CGFloat r = 0.5; + CGFloat rr = r * (1.0 + percentInflection); + + BOOL firstPoint = YES; + for (int i = 0; i < numberOfInflections; i++) + { + CGFloat theta = i * TWO_PI / numberOfInflections; + CGFloat dTheta = TWO_PI / numberOfInflections; + + if (firstPoint) + { + CGFloat xa = center.x + r * sin(theta); + CGFloat ya = center.y + r * cos(theta); + CGPoint pa = CGPointMake(xa, ya); + [path moveToPoint:pa]; + firstPoint = NO; + } + + CGFloat cp1x = center.x + rr * sin(theta + dTheta / 3); + CGFloat cp1y = center.y + rr * cos(theta + dTheta / 3); + CGPoint cp1 = CGPointMake(cp1x, cp1y); + + CGFloat cp2x = center.x + rr * sin(theta + 2 * dTheta / 3); + CGFloat cp2y = center.y + rr * cos(theta + 2 * dTheta / 3); + CGPoint cp2 = CGPointMake(cp2x, cp2y); + + CGFloat xb = center.x + r * sin(theta + dTheta); + CGFloat yb = center.y + r * cos(theta + dTheta); + CGPoint pb = CGPointMake(xb, yb); + + [path addCurveToPoint:pb controlPoint1:cp1 controlPoint2:cp2]; + } + + [path closePath]; + + return path; +} + +UIBezierPath *BezierStarShape(NSUInteger numberOfInflections, CGFloat percentInflection) +{ + if (numberOfInflections < 3) + { + NSLog(@"Error: Please supply at least 3 inflections"); + return nil; + } + + UIBezierPath *path = [UIBezierPath bezierPath]; + CGRect destinationRect = CGRectMake(0, 0, 1, 1); + CGPoint center = RectGetCenter(destinationRect); + CGFloat r = 0.5; + CGFloat rr = r * (1.0 + percentInflection); + + BOOL firstPoint = YES; + for (int i = 0; i < numberOfInflections; i++) + { + CGFloat theta = i * TWO_PI / numberOfInflections; + CGFloat dTheta = TWO_PI / numberOfInflections; + + if (firstPoint) + { + CGFloat xa = center.x + r * sin(theta); + CGFloat ya = center.y + r * cos(theta); + CGPoint pa = CGPointMake(xa, ya); + [path moveToPoint:pa]; + firstPoint = NO; + } + + CGFloat cp1x = center.x + rr * sin(theta + dTheta / 2); + CGFloat cp1y = center.y + rr * cos(theta + dTheta / 2); + CGPoint cp1 = CGPointMake(cp1x, cp1y); + + CGFloat xb = center.x + r * sin(theta + dTheta); + CGFloat yb = center.y + r * cos(theta + dTheta); + CGPoint pb = CGPointMake(xb, yb); + + [path addLineToPoint:cp1]; + [path addLineToPoint:pb]; + } + + [path closePath]; + + return path; +} + +#pragma mark - Shadows + +// Establish context shadow state +void SetShadow(UIColor *color, CGSize size, CGFloat blur) +{ + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + if (color) + CGContextSetShadowWithColor(context, size, blur, color.CGColor); + else + CGContextSetShadow(context, size, blur); +} + +// Draw *only* the shadow +void DrawShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + // Build shadow + PushDraw(^{ + SetShadow(color, CGSizeMake(size.width, size.height), blur); + [path.inverse addClip]; + [path fill:color]; + }); +} + +// Draw shadow inside shape +void DrawInnerShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + // Build shadow + PushDraw(^{ + SetShadow(color, CGSizeMake(size.width, size.height), blur); + [path addClip]; + [path.inverse fill:color]; + }); +} + +#pragma mark - Photoshop Style Effects +UIColor *ContrastColor(UIColor *color) +{ + if (CGColorSpaceGetNumberOfComponents(CGColorGetColorSpace(color.CGColor)) == 3) + { + CGFloat r, g, b, a; + [color getRed:&r green:&g blue:&b alpha:&a]; + CGFloat luminance = r * 0.2126f + g * 0.7152f + b * 0.0722f; + return (luminance > 0.5f) ? [UIColor blackColor] : [UIColor whiteColor]; + } + + CGFloat w, a; + [color getWhite:&w alpha:&a]; + return (w > 0.5f) ? [UIColor blackColor] : [UIColor whiteColor]; +} + +// Create 3d embossed effect +// Typically call with black color at 0.5 +void EmbossPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat blur) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + + UIColor *contrast = ContrastColor(color); + DrawInnerShadow(path, contrast, CGSizeMake(-radius, radius), blur); + DrawInnerShadow(path, color, CGSizeMake(radius, -radius), blur); +} + +// Half an emboss +void InnerBevel(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGFloat x = radius * sin(theta); + CGFloat y = radius * cos(theta); + + UIColor *shadowColor = [color colorWithAlphaComponent:0.5f]; + DrawInnerShadow(path, shadowColor, CGSizeMake(-x, y), 2); +} + +// I don't love this +void ExtrudePath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + CGFloat x = radius * sin(theta); + CGFloat y = radius * cos(theta); + DrawShadow(path, color, CGSizeMake(x, y), 0); +} + +// Typically call with black color at 0.5 +void BevelPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGFloat x = radius * sin(theta); + CGFloat y = radius * cos(theta); + DrawInnerShadow(path, color, CGSizeMake(-x, y), 2); + DrawShadow(path, color, CGSizeMake(x / 2 , -y / 2), 0); +} + +@implementation UIBezierPath (HandyUtilities) +#pragma mark - Bounds +- (CGPoint) center +{ + return PathBoundingCenter(self); +} + +- (CGRect) computedBounds +{ + return PathBoundingBox(self); +} + +- (CGRect) computedBoundsWithLineWidth +{ + return PathBoundingBoxWithLineWidth(self); +} + +#pragma mark - Stroking and Filling + +- (void) addDashes +{ + AddDashesToPath(self); +} + +- (void) addDashes: (NSArray *) pattern +{ + if (!pattern.count) return; + CGFloat *dashes = malloc(pattern.count * sizeof(CGFloat)); + for (int i = 0; i < pattern.count; i++) + dashes[i] = [pattern[i] floatValue]; + [self setLineDash:dashes count:pattern.count phase:0]; + free(dashes); +} + +- (void) applyPathPropertiesToContext +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGContextSetLineWidth(context, self.lineWidth); + CGContextSetLineCap(context, self.lineCapStyle); + CGContextSetLineJoin(context, self.lineJoinStyle); + CGContextSetMiterLimit(context, self.miterLimit); + CGContextSetFlatness(context, self.flatness); + + NSInteger count; + [self getLineDash:NULL count:&count phase:NULL]; + + CGFloat phase; + CGFloat *pattern = malloc(count * sizeof(CGFloat)); + [self getLineDash:pattern count:&count phase:&phase]; + CGContextSetLineDash(context, phase, pattern, count); + free(pattern); +} + +- (void) stroke: (CGFloat) width color: (UIColor *) color +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + PushDraw(^{ + if (color) [color setStroke]; + CGFloat holdWidth = self.lineWidth; + if (width > 0) + self.lineWidth = width; + [self stroke]; + self.lineWidth = holdWidth; + }); +} + +- (void) stroke: (CGFloat) width +{ + [self stroke:width color:nil]; +} + +- (void) strokeInside: (CGFloat) width color: (UIColor *) color +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + PushDraw(^{ + [self addClip]; + [self stroke:width * 2 color:color]; + }); +} + +- (void) strokeInside: (CGFloat) width +{ + [self strokeInside:width color:nil]; +} + +- (void) fill: (UIColor *) fillColor +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + PushDraw(^{ + if (fillColor) + [fillColor set]; + [self fill]; + }); +} + +- (void) fill: (UIColor *) fillColor withMode: (CGBlendMode) blendMode +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + PushDraw(^{ + CGContextSetBlendMode(context, blendMode); + [self fill:fillColor]; + }); +} + +- (void) fillWithNoise: (UIColor *) fillColor +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + [self fill:fillColor]; + [self fill:[NoiseColor() colorWithAlphaComponent:0.05f] withMode:kCGBlendModeScreen]; +} + +#pragma mark - Clippage +- (void) clipToPath +{ + [self addClip]; +} + +- (void) clipToStroke:(NSUInteger)width +{ + CGPathRef pathRef = CGPathCreateCopyByStrokingPath(self.CGPath, NULL, width, kCGLineCapButt, kCGLineJoinMiter, 4); + UIBezierPath *clipPath = [UIBezierPath bezierPathWithCGPath:pathRef]; + CGPathRelease(pathRef); + [clipPath addClip]; +} + +#pragma mark - Misc + +- (UIBezierPath *) safeCopy +{ + UIBezierPath *p = [UIBezierPath bezierPath]; + [p appendPath:self]; + CopyBezierState(self, p); + return p; +} +@end diff --git a/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h b/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h new file mode 100644 index 000000000..d70a79177 --- /dev/null +++ b/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h @@ -0,0 +1,56 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import "Bezier.h" + +// Construct path +UIBezierPath *BezierPathWithElements(NSArray *elements); +UIBezierPath *BezierPathWithPoints(NSArray *points); +UIBezierPath *InterpolatedPath(UIBezierPath *path); + +// Partial paths +UIBezierPath *CroppedPath(UIBezierPath *path, CGFloat percent); +UIBezierPath *PathFromPercentToPercent(UIBezierPath *path, CGFloat startPercent, CGFloat endPercent); + +/* + + UIBezierPath - Elements Category + + */ + +@interface UIBezierPath (Elements) + +@property (nonatomic, readonly) NSArray *elements; +@property (nonatomic, readonly) NSArray *subpaths; + +@property (nonatomic, readonly) NSArray *destinationPoints; +@property (nonatomic, readonly) NSArray *interpolatedPathPoints; + +@property (nonatomic, readonly) NSUInteger count; +- (id)objectAtIndexedSubscript:(NSUInteger)idx; + +@property (nonatomic, readonly) CGPoint center; +@property (nonatomic, readonly) CGRect calculatedBounds; + +@property (nonatomic, readonly) UIBezierPath *reversed; +@property (nonatomic, readonly) UIBezierPath *inverse; +@property (nonatomic, readonly) UIBezierPath *boundedInverse; + +@property (nonatomic, readonly) BOOL subpathIsClosed; +- (BOOL) closeSafely; + +// Measure length +@property (nonatomic, readonly) CGFloat pathLength; +- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope; + +// String Representations +- (void) showTheCode; +- (NSString *) stringValue; + +// -- Invert path to arbitrary rectangle +- (UIBezierPath *) inverseInRect: (CGRect) rect; +@end \ No newline at end of file diff --git a/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m b/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m new file mode 100644 index 000000000..e951931d9 --- /dev/null +++ b/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m @@ -0,0 +1,519 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "UIBezierPath+Elements.h" +#import "BaseGeometry.h" + +#pragma mark - Construction + +// Return Bezier path built with the supplied elements +UIBezierPath *BezierPathWithElements(NSArray *elements) +{ + UIBezierPath *path = [UIBezierPath bezierPath]; + for (BezierElement *element in elements) + [element addToPath:path]; + return path; +} + + +UIBezierPath *InterpolatedPath(UIBezierPath *path) +{ + return BezierPathWithPoints(path.interpolatedPathPoints); +} + +#define POINT(_X_) ([(NSValue *)points[_X_] CGPointValue]) + +// Pass array with NSValue'd CGRect points +UIBezierPath *BezierPathWithPoints(NSArray *points) +{ + UIBezierPath *path = [UIBezierPath bezierPath]; + if (!points.count) + return path; + [path moveToPoint:POINT(0)]; + for (int i = 1; i < points.count; i++) + [path addLineToPoint:POINT(i)]; + [path closePath]; + return path; +} + +#pragma mark - Partial Path + +UIBezierPath *CroppedPath(UIBezierPath *path, CGFloat percent) +{ + NSArray *elements = path.elements; + if (elements.count == 0) return path; + + int targetCount = elements.count * percent; + NSArray *targetElements = [elements subarrayWithRange:NSMakeRange(0, targetCount)]; + UIBezierPath *outputPath = BezierPathWithElements(targetElements); + return outputPath; +} + +UIBezierPath *PathFromPercentToPercent(UIBezierPath *path, CGFloat startPercent, CGFloat endPercent) +{ + NSArray *elements = path.elements; + if (elements.count == 0) return path; + + int targetCount = elements.count * endPercent; + NSArray *targetElements = [elements subarrayWithRange:NSMakeRange(0, targetCount)]; + UIBezierPath *outputPath = BezierPathWithElements(targetElements); + return outputPath; +} + +#pragma mark - Bezier Elements Category - + +@implementation UIBezierPath (Elements) + +// Convert one element to BezierElement and save to array +void GetBezierElements(void *info, const CGPathElement *element) +{ + NSMutableArray *bezierElements = (__bridge NSMutableArray *)info; + if (element) + [bezierElements addObject:[BezierElement elementWithPathElement:*element]]; +} + +// Retrieve array of component elements +- (NSArray *) elements +{ + NSMutableArray *elements = [NSMutableArray array]; + CGPathApply(self.CGPath, (__bridge void *)elements, GetBezierElements); + return elements; +} + +#pragma mark - Subpaths +// Subpaths must be well defined +- (NSMutableArray *) subpaths +{ + NSMutableArray *results = [NSMutableArray array]; + UIBezierPath *current = nil; + NSArray *elements = self.elements; + + for (BezierElement *element in elements) + { + if (element.elementType == kCGPathElementCloseSubpath) + { + [current closePath]; + if (current) + [results addObject:current]; + current = nil; + continue; + } + + if (element.elementType == kCGPathElementMoveToPoint) + { + if (current) + [results addObject:current]; + + current = [UIBezierPath bezierPath]; + [current moveToPoint:element.point]; + continue; + } + + if (current) + [element addToPath:current]; + else + { + NSLog(@"Error: cannot add element to nil path: %@", element.stringValue); + continue; + } + } + + if (current) + [results addObject:current]; + + return results; +} + + +// Only collect those points that have destinations +- (NSArray *) destinationPoints +{ + NSMutableArray *array = [NSMutableArray array]; + NSArray *elements = self.elements; + + for (BezierElement *element in elements) + if (!POINT_IS_NULL(element.point)) + [array addObject:[NSValue valueWithCGPoint:element.point]]; + + return array; +} + +// Points and interpolated points +- (NSArray *) interpolatedPathPoints +{ + NSMutableArray *points = [NSMutableArray array]; + BezierElement *current = nil; + int overkill = 3; + for (BezierElement *element in self.elements) + { + switch (element.elementType) + { + case kCGPathElementMoveToPoint: + case kCGPathElementAddLineToPoint: + [points addObject:[NSValue valueWithCGPoint:element.point]]; + current = element; + break; + case kCGPathElementCloseSubpath: + current = nil; + break; + case kCGPathElementAddCurveToPoint: + { + for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++) + { + CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill); + CGPoint p = CubicBezierPoint(percent, current.point, element.controlPoint1, element.controlPoint2, element.point); + [points addObject:[NSValue valueWithCGPoint:p]]; + } + [points addObject:[NSValue valueWithCGPoint:element.point]]; + current = element; + break; + } + case kCGPathElementAddQuadCurveToPoint: + { + for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++) + { + CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill); + CGPoint p = QuadBezierPoint(percent, current.point, element.controlPoint1, element.point); + [points addObject:[NSValue valueWithCGPoint:p]]; + } + [points addObject:[NSValue valueWithCGPoint:element.point]]; + current = element; + break; + } + } + } + return points; +} + +#pragma mark - Array Access + +- (NSUInteger) count +{ + return self.elements.count; +} + +- (id)objectAtIndexedSubscript:(NSUInteger)idx +{ + NSArray *elements = self.elements; + if (idx >= elements.count) + return nil; + return elements[idx]; +} + +#pragma mark - Geometry Workaround +- (CGRect) calculatedBounds +{ + // Thank you Ryan Petrich + return CGPathGetPathBoundingBox(self.CGPath); +} + +// Return center of bounds +- (CGPoint) center +{ + return RectGetCenter(self.calculatedBounds); +} + +#pragma mark - Reversal Workaround +- (UIBezierPath *) reverseSubpath: (UIBezierPath *) subpath +{ + NSArray *elements = subpath.elements; + NSArray *reversedElements = [[elements reverseObjectEnumerator] allObjects]; + + UIBezierPath *newPath = [UIBezierPath bezierPath]; + CopyBezierState(self, newPath); + BOOL closesSubpath = NO; + + BezierElement *firstElement; + for (BezierElement *e in elements) + { + if (!POINT_IS_NULL(e.point)) + { + firstElement = e; + break; + } + } + + BezierElement *lastElement; + for (BezierElement *e in reversedElements) + { + if (!POINT_IS_NULL(e.point)) + { + lastElement = e; + break; + } + } + + BezierElement *element = [elements lastObject]; + if (element.elementType == kCGPathElementCloseSubpath) + { + if (firstElement) + [newPath moveToPoint:firstElement.point]; + + if (lastElement) + [newPath addLineToPoint:lastElement.point]; + + closesSubpath = YES; + } + else + { + [newPath moveToPoint:lastElement.point]; + } + + CFIndex i = 0; + for (BezierElement *element in reversedElements) + { + i++; + BezierElement *nextElement = nil; + BezierElement *workElement = [element copy]; + + if (element.elementType == kCGPathElementCloseSubpath) + continue; + + if (element == firstElement) + { + if (closesSubpath) [newPath closePath]; + continue; + } + + if (i < reversedElements.count) + { + nextElement = reversedElements[i]; + if (!POINT_IS_NULL(workElement.controlPoint2)) + { + CGPoint tmp = workElement.controlPoint1; + workElement.controlPoint1 = workElement.controlPoint2; + workElement.controlPoint2 = tmp; + } + workElement.point = nextElement.point; + } + + if (element.elementType == kCGPathElementMoveToPoint) + workElement.elementType = kCGPathElementAddLineToPoint; + + [workElement addToPath:newPath]; + } + + return newPath; + +} + +- (UIBezierPath *) reversed +{ + // [self bezierPathByReversingPath] seriously does not work the + // way you expect. Radars are filed. + + UIBezierPath *reversed = [UIBezierPath bezierPath]; + NSArray *reversedSubpaths = [[self.subpaths reverseObjectEnumerator] allObjects]; + + for (UIBezierPath *subpath in reversedSubpaths) + { + UIBezierPath *p = [self reverseSubpath:subpath]; + if (p) + [reversed appendPath:p]; + } + return reversed; +} + +#pragma mark - Closing +- (BOOL) subpathIsClosed +{ + NSArray *elements = self.elements; + + // A legal closed path must contain 3 elements + // move, add, close + if (elements.count < 3) + return NO; + + BezierElement *element = [elements lastObject]; + return element.elementType == kCGPathElementCloseSubpath; +} + +- (BOOL) closeSafely +{ + NSArray *elements = self.elements; + if (elements.count < 2) + return NO; + + BezierElement *element = [elements lastObject]; + if (element.elementType != kCGPathElementCloseSubpath) + { + [self closePath]; + return YES; + } + + return NO; +} + + +#pragma mark - Show the Code +- (void) showTheCode +{ + + printf("\n- (UIBezierPath *) buildBezierPath\n"); + printf("{\n"); + printf(" UIBezierPath *path = [UIBezierPath bezierPath];\n\n"); + + NSArray *elements = self.elements; + for (BezierElement *element in elements) + [element showTheCode]; + + printf(" return path;\n"); + printf("}\n\n"); +} + +- (NSString *) stringValue +{ + NSMutableString *string = [NSMutableString stringWithString:@"\n"]; + NSArray *elements = self.elements; + for (BezierElement *element in elements) + [string appendFormat:@"%@\n", element.stringValue]; + + return string; +} + +#pragma mark - Transformations +// Project point from native to dest +CGPoint adjustPoint(CGPoint p, CGRect native, CGRect dest) +{ + CGFloat scaleX = dest.size.width / native.size.width; + CGFloat scaleY = dest.size.height / native.size.height; + + CGPoint point = PointSubtractPoint(p, native.origin); + point.x *= scaleX; + point.y *= scaleY; + CGPoint destPoint = PointAddPoint(point, dest.origin); + + return destPoint; +} + +// Adjust points by applying block to each element +- (UIBezierPath *) adjustPathElementsWithBlock: (PathBlock) block +{ + UIBezierPath *path = [UIBezierPath bezierPath]; + if (!block) + { + [path appendPath:self]; + return path; + } + + for (BezierElement *element in self.elements) + [[element elementByApplyingBlock:block] addToPath:path]; + + return path; +} + +// Apply transform +- (UIBezierPath *) pathApplyTransform: (CGAffineTransform) transform +{ + UIBezierPath *copy = [UIBezierPath bezierPath]; + [copy appendPath:self]; + + CGRect bounding = self.calculatedBounds; + CGPoint center = RectGetCenter(bounding); + CGAffineTransform t = CGAffineTransformIdentity; + t = CGAffineTransformTranslate(t, center.x, center.y); + t = CGAffineTransformConcat(transform, t); + t = CGAffineTransformTranslate(t, -center.x, -center.y); + [copy applyTransform:t]; + + return copy; +} + +- (CGFloat) pathLength +{ + NSArray *elements = self.elements; + CGPoint current = NULLPOINT; + CGPoint firstPoint = NULLPOINT; + float totalPointLength = 0.0f; + + for (BezierElement *element in elements) + { + totalPointLength += ElementDistanceFromPoint(element, current, firstPoint); + + if (element.elementType == kCGPathElementMoveToPoint) + firstPoint = element.point; + else if (element.elementType == kCGPathElementCloseSubpath) + firstPoint = NULLPOINT; + + if (element.elementType != kCGPathElementCloseSubpath) + current = element.point; + } + + return totalPointLength; +} + + +// Retrieve the point and slope at a given percent offset -- This is expensive +- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope +{ + NSArray *elements = self.elements; + + if (percent == 0.0f) + { + BezierElement *first = [elements objectAtIndex:0]; + return first.point; + } + + float pathLength = self.pathLength; + float totalDistance = 0.0f; + + CGPoint current = NULLPOINT; + CGPoint firstPoint = NULLPOINT; + + for (BezierElement *element in elements) + { + float distance = ElementDistanceFromPoint(element, current, firstPoint); + CGFloat proposedTotalDistance = totalDistance + distance; + CGFloat proposedPercent = proposedTotalDistance / pathLength; + + if (proposedPercent < percent) + { + // consume and continue + totalDistance = proposedTotalDistance; + + if (element.elementType == kCGPathElementMoveToPoint) + firstPoint = element.point; + + current = element.point; + + continue; + } + + // What percent between p1 and p2? + CGFloat currentPercent = totalDistance / pathLength; + CGFloat dPercent = percent - currentPercent; + CGFloat percentDistance = dPercent * pathLength; + CGFloat targetPercent = percentDistance / distance; + + // Return result + CGPoint point = InterpolatePointFromElement(element, current, firstPoint, targetPercent, slope); + return point; + } + + return NULLPOINT; +} + +#pragma mark - Inverses +- (UIBezierPath *) inverseInRect: (CGRect) rect +{ + UIBezierPath *path = [UIBezierPath bezierPath]; + CopyBezierState(self, path); + [path appendPath:self]; + [path appendPath:[UIBezierPath bezierPathWithRect:rect]]; + path.usesEvenOddFillRule = YES; + return path; +} + +- (UIBezierPath *) inverse +{ + return [self inverseInRect:CGRectInfinite]; +} + +- (UIBezierPath *) boundedInverse +{ + return [self inverseInRect:self.bounds]; +} +@end diff --git a/ios/QuartzBookPack/Drawing/Drawing-Block.h b/ios/QuartzBookPack/Drawing/Drawing-Block.h new file mode 100644 index 000000000..0f7efb8bf --- /dev/null +++ b/ios/QuartzBookPack/Drawing/Drawing-Block.h @@ -0,0 +1,22 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import + +typedef void (^DrawingBlock)(CGRect bounds); +typedef void (^DrawingStateBlock)(); +void PushDraw(DrawingStateBlock block); +void PushLayerDraw(DrawingStateBlock block); + + +// Image +UIImage *ImageWithBlock(DrawingBlock block, CGSize size); +UIImage *DrawIntoImage(CGSize size, DrawingStateBlock block); + + +// Blurring +void DrawAndBlur(CGFloat radius, DrawingStateBlock block); diff --git a/ios/QuartzBookPack/Drawing/Drawing-Block.m b/ios/QuartzBookPack/Drawing/Drawing-Block.m new file mode 100644 index 000000000..0fcaa8c74 --- /dev/null +++ b/ios/QuartzBookPack/Drawing/Drawing-Block.m @@ -0,0 +1,71 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "Drawing-Block.h" +#import "Utility.h" + +#pragma mark - Drawing +UIImage *ImageWithBlock(DrawingBlock block, CGSize size) +{ + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + if (block) block((CGRect){.size = size}); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +void PushDraw(DrawingStateBlock block) +{ + if (!block) return; // nothing to do + + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGContextSaveGState(context); + block(); + CGContextRestoreGState(context); +} + +// Improve performance by pre-clipping context +// before beginning layer drawing +void PushLayerDraw(DrawingStateBlock block) +{ + if (!block) return; // nothing to do + + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGContextBeginTransparencyLayer(context, NULL); + block(); + CGContextEndTransparencyLayer(context); +} + +UIImage *DrawIntoImage(CGSize size, DrawingStateBlock block) +{ + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + if (block) block(); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + + +#define DEBUG_IMAGE(_IMAGE_, _NAME_) [UIImagePNGRepresentation(_IMAGE_) writeToFile:[NSString stringWithFormat:@"/Users/ericasadun/Desktop/%@.png", _NAME_] atomically:YES] + +// Create a blurred drawing group +// Listing 7-4 +void DrawAndBlur(CGFloat radius, DrawingStateBlock block) +{ + if (!block) return; // nothing to do + + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + // Draw and blur the image + UIImage *baseImage = DrawIntoImage(GetUIKitContextSize(), block); + UIImage *blurred = GaussianBlurImage(baseImage, radius); + [blurred drawAtPoint:CGPointZero]; +} \ No newline at end of file diff --git a/ios/QuartzBookPack/Drawing/Drawing-Gradient.h b/ios/QuartzBookPack/Drawing/Drawing-Gradient.h new file mode 100644 index 000000000..41d29c38e --- /dev/null +++ b/ios/QuartzBookPack/Drawing/Drawing-Gradient.h @@ -0,0 +1,44 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import "BezierFunctions.h" + +#define COLOR_LEVEL(_selector_, _alpha_) [([UIColor _selector_])colorWithAlphaComponent:_alpha_] +#define WHITE_LEVEL(_amt_, _alpha_) [UIColor colorWithWhite:(_amt_) alpha:(_alpha_)] + +// Gradient drawing styles +#define LIMIT_GRADIENT_EXTENT 0 +#define BEFORE_START kCGGradientDrawsBeforeStartLocation +#define AFTER_END kCGGradientDrawsAfterEndLocation +#define KEEP_DRAWING kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation + +typedef __attribute__((NSObject)) CGGradientRef GradientObject; + +@interface Gradient : NSObject +@property (nonatomic, readonly) CGGradientRef gradient; ++ (instancetype) gradientWithColors: (NSArray *) colors locations: (NSArray *) locations; ++ (instancetype) gradientFrom: (UIColor *) color1 to: (UIColor *) color2; + ++ (instancetype) rainbow; ++ (instancetype) linearGloss:(UIColor *) color; ++ (instancetype) gradientUsingInterpolationBlock: (InterpolationBlock) block between: (UIColor *) c1 and: (UIColor *) c2; ++ (instancetype) easeInGradientBetween: (UIColor *) c1 and:(UIColor *) c2; ++ (instancetype) easeInOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2; ++ (instancetype) easeOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2; + +- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 style: (int) mask; +- (void) drawRadialFrom:(CGPoint) p1 toPoint: (CGPoint) p2 radii: (CGPoint) radii style: (int) mask; + +- (void) drawTopToBottom: (CGRect) rect; +- (void) drawBottomToTop: (CGRect) rect; +- (void) drawLeftToRight: (CGRect) rect; +- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2; +- (void) drawAlongAngle: (CGFloat) angle in:(CGRect) rect; + +- (void) drawBasicRadial: (CGRect) rect; +- (void) drawRadialFrom: (CGPoint) p1 toPoint: (CGPoint) p2; +@end; diff --git a/ios/QuartzBookPack/Drawing/Drawing-Gradient.m b/ios/QuartzBookPack/Drawing/Drawing-Gradient.m new file mode 100644 index 000000000..9e85897e5 --- /dev/null +++ b/ios/QuartzBookPack/Drawing/Drawing-Gradient.m @@ -0,0 +1,256 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "Drawing-Gradient.h" +#import "Utility.h" + +@interface Gradient () +@property (nonatomic, strong) GradientObject storedGradient; +@end + +@implementation Gradient + +#pragma mark - Internal +- (CGGradientRef) gradient +{ + return _storedGradient; +} + +#pragma mark - Convenience Creation ++ (instancetype) gradientWithColors: (NSArray *) colorsArray locations: (NSArray *) locationArray +{ + if (!colorsArray) COMPLAIN_AND_BAIL_NIL(@"Missing colors array", nil); + if (!locationArray) COMPLAIN_AND_BAIL_NIL(@"Missing location array", nil); + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + if (space == NULL) + { + NSLog(@"Error: Unable to create device RGB color space"); + return nil; + } + + // Convert locations to CGFloat * + CGFloat locations[locationArray.count]; + for (int i = 0; i < locationArray.count; i++) + locations[i] = [locationArray[i] floatValue]; + + // Convert colors to (id) CGColorRef + NSMutableArray *colorRefArray = [NSMutableArray array]; + for (UIColor *color in colorsArray) + [colorRefArray addObject:(id)color.CGColor]; + + CGGradientRef gradientRef = CGGradientCreateWithColors(space, (__bridge CFArrayRef) colorRefArray, locations); + CGColorSpaceRelease(space); + + if (gradientRef == NULL) + { + NSLog(@"Error: Unable to construct CGGradientRef"); + return nil; + } + + Gradient *gradient = [[self alloc] init]; + gradient.storedGradient = gradientRef; + CGGradientRelease(gradientRef); + + return gradient; +} + ++ (instancetype) gradientFrom: (UIColor *) color1 to: (UIColor *) color2 +{ + return [self gradientWithColors:@[color1, color2] locations:@[@(0.0f), @(1.0f)]]; +} + +#pragma mark - Linear + +- (void) drawRadialFrom:(CGPoint) p1 toPoint: (CGPoint) p2 radii: (CGPoint) radii style: (int) mask +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGContextDrawRadialGradient(context, self.gradient, p1, radii.x, p2, radii.y, mask); +} + +- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 style: (int) mask +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGContextDrawLinearGradient(context, self.gradient, p1, p2, mask); +} + +- (void) drawLeftToRight: (CGRect) rect +{ + CGPoint p1 = RectGetMidLeft(rect); + CGPoint p2 = RectGetMidRight(rect); + [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; +} + +- (void) drawTopToBottom: (CGRect) rect +{ + CGPoint p1 = RectGetMidTop(rect); + CGPoint p2 = RectGetMidBottom(rect); + [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; +} + +- (void) drawBottomToTop:(CGRect)rect +{ + CGPoint p1 = RectGetMidBottom(rect); + CGPoint p2 = RectGetMidTop(rect); + [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; +} + +- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 +{ + [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; +} + +- (void) drawAlongAngle: (CGFloat) theta in:(CGRect) rect +{ + CGPoint center = RectGetCenter(rect); + CGFloat r = PointDistanceFromPoint(center, RectGetTopRight(rect)); + + CGFloat phi = theta + M_PI; + if (phi > TWO_PI) + phi -= TWO_PI; + + CGFloat dx1 = r * sin(theta); + CGFloat dy1 = r * cos(theta); + CGFloat dx2 = r * sin(phi); + CGFloat dy2 = r * cos(phi); + + CGPoint p1 = CGPointMake(center.x + dx1, center.y + dy1); + CGPoint p2 = CGPointMake(center.x + dx2, center.y + dy2); + [self drawFrom:p1 toPoint:p2]; +} + +#pragma mark - Radial +- (void) drawBasicRadial: (CGRect) rect +{ + CGPoint p1 = RectGetCenter(rect); + CGFloat r = CGRectGetWidth(rect) / 2; + [self drawRadialFrom:p1 toPoint:p1 radii:CGPointMake(0, r) style:KEEP_DRAWING]; +} + +- (void) drawRadialFrom: (CGPoint) p1 toPoint: (CGPoint) p2; +{ + [self drawRadialFrom:p1 toPoint:p1 radii:CGPointMake(0, PointDistanceFromPoint(p1, p2)) style:KEEP_DRAWING]; +} + +#pragma mark - Prebuilt ++ (instancetype) rainbow +{ + NSMutableArray *colors = [NSMutableArray array]; + NSMutableArray *locations = [NSMutableArray array]; + int n = 24; + for (int i = 0; i <= n; i++) + { + CGFloat percent = (CGFloat) i / (CGFloat) n; + CGFloat colorDistance = percent * (CGFloat) (n - 1) / (CGFloat) n; + UIColor *color = [UIColor colorWithHue:colorDistance saturation:1 brightness:1 alpha:1]; + [colors addObject:color]; + [locations addObject:@(percent)]; + } + + return [Gradient gradientWithColors:colors locations:locations]; +} + ++ (instancetype) linearGloss:(UIColor *) color +{ + CGFloat r, g, b, a; + [color getRed:&r green:&g blue:&b alpha:&a]; + + CGFloat l = (0.299f * r + 0.587f * g + 0.114f * b); + CGFloat gloss = pow(l, 0.2) * 0.5; + + CGFloat h, s, v; + [color getHue:&h saturation:&s brightness:&v alpha:NULL]; + s = fminf(s, 0.2f); + + // Rotate by 0.6 PI + CGFloat rHue = ((h < 0.95) && (h > 0.7)) ? 0.67 : 0.17; + CGFloat phi = rHue * M_PI * 2; + CGFloat theta = h * M_PI; + + // Interpolate distance + CGFloat dTheta = (theta - phi); + while (dTheta < 0) dTheta += M_PI * 2; + while (dTheta > 2 * M_PI) dTheta -= M_PI_2; + CGFloat factor = 0.7 + 0.3 * cosf(dTheta); + + // Build highlight colors + UIColor *c1 = [UIColor colorWithHue:h * factor + (1 - factor) * rHue saturation:s brightness:v * factor + (1 - factor) alpha:gloss]; + UIColor *c2 = [c1 colorWithAlphaComponent:0]; + + // Build gradient + NSArray *colors = @[WHITE_LEVEL(1, gloss), WHITE_LEVEL(1, 0.2), c2, c1]; + NSArray *locations = @[@(0.0), @(0.5), @(0.5), @(1)]; + + return [Gradient gradientWithColors:colors locations:locations]; +} + +UIColor *InterpolateBetweenColors(UIColor *c1, UIColor *c2, CGFloat amt) +{ + CGFloat r1, g1, b1, a1; + CGFloat r2, g2, b2, a2; + + if (CGColorGetNumberOfComponents(c1.CGColor) == 4) + [c1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1]; + else + { + [c1 getWhite:&r1 alpha:&a1]; + g1 = r1; b1 = r1; + } + + if (CGColorGetNumberOfComponents(c2.CGColor) == 4) + [c2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2]; + else + { + [c2 getWhite:&r2 alpha:&a2]; + g2 = r2; b2 = r2; + } + + CGFloat r = (r2 * amt) + (r1 * (1.0 - amt)); + CGFloat g = (g2 * amt) + (g1 * (1.0 - amt)); + CGFloat b = (b2 * amt) + (b1 * (1.0 - amt)); + CGFloat a = (a2 * amt) + (a1 * (1.0 - amt)); + return [UIColor colorWithRed:r green:g blue:b alpha:a]; +} + + ++ (instancetype) gradientUsingInterpolationBlock: (InterpolationBlock) block between: (UIColor *) c1 and: (UIColor *) c2; +{ + if (!block) + COMPLAIN_AND_BAIL_NIL(@"Must pass interpolation block", nil); + + NSMutableArray *colors = [NSMutableArray array]; + NSMutableArray *locations = [NSMutableArray array]; + int numberOfSamples = 24; + for (int i = 0; i <= numberOfSamples; i++) + { + CGFloat amt = (CGFloat) i / (CGFloat) numberOfSamples; + CGFloat percentage = Clamp(block(amt), 0.0, 1.0); + [colors addObject:InterpolateBetweenColors(c1, c2, percentage)]; + [locations addObject:@(amt)]; + } + + return [Gradient gradientWithColors:colors locations:locations]; +} + ++ (instancetype) easeInGradientBetween: (UIColor *) c1 and:(UIColor *) c2 +{ + return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseIn(percent, 3);} between:c1 and:c2]; +} + ++ (instancetype) easeInOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2 +{ + return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseInOut(percent, 3);} between:c1 and:c2]; +} + ++ (instancetype) easeOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2 +{ + return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseOut(percent, 3);} between:c1 and:c2]; +} +@end diff --git a/ios/QuartzBookPack/Drawing/Drawing-Util.h b/ios/QuartzBookPack/Drawing/Drawing-Util.h new file mode 100644 index 000000000..fd6ea40af --- /dev/null +++ b/ios/QuartzBookPack/Drawing/Drawing-Util.h @@ -0,0 +1,25 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import "Drawing-Gradient.h" + +UIColor *ScaleColorBrightness(UIColor *color, CGFloat amount); + +void DrawStrokedShadowedShape(UIBezierPath *path, UIColor *baseColor, CGRect dest); +void DrawStrokedShadowedText(NSString *string, NSString *fontFace, UIColor *baseColor, CGRect dest); + +void DrawIndentedPath(UIBezierPath *path, UIColor *primary, CGRect rect); +void DrawIndentedText(NSString *string, NSString *fontFace, UIColor *primary, CGRect rect); + +void DrawGradientOverTexture(UIBezierPath *path, UIImage *texture, Gradient *gradient, CGFloat alpha); +void DrawBottomGlow(UIBezierPath *path, UIColor *color, CGFloat percent); +void DrawIconTopLight(UIBezierPath *path, CGFloat p); + +CGSize GetUIKitContextSize(); +UIImage *GradientMaskedReflectionImage(UIImage *sourceImage); +void DrawGradientMaskedReflection(UIImage *image, CGRect rect);; +void ApplyMaskToContext(UIImage *mask); diff --git a/ios/QuartzBookPack/Drawing/Drawing-Util.m b/ios/QuartzBookPack/Drawing/Drawing-Util.m new file mode 100644 index 000000000..46005dd7b --- /dev/null +++ b/ios/QuartzBookPack/Drawing/Drawing-Util.m @@ -0,0 +1,241 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "Drawing-Util.h" +#import "Utility.h" + +UIColor *ScaleColorBrightness(UIColor *color, CGFloat amount) +{ + CGFloat h, s, v, a; + [color getHue:&h saturation:&s brightness:&v alpha:&a]; + CGFloat v1 = Clamp(v * amount, 0, 1); + return [UIColor colorWithHue:h saturation:s brightness:v1 alpha:a]; +} + +void DrawStrokedShadowedShape(UIBezierPath *path, UIColor *baseColor, CGRect dest) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (!context) COMPLAIN_AND_BAIL(@"No context to draw to", nil); + + PushDraw(^{ + CGContextSetShadow(context, CGSizeMake(4, 4), 4); + + PushLayerDraw(^{ + + // Draw letter gradient (to half brightness) + PushDraw(^{ + Gradient *innerGradient = [Gradient gradientFrom:baseColor to:ScaleColorBrightness(baseColor, 0.5)]; + [path addClip]; + [innerGradient drawTopToBottom:path.bounds]; + }); + + // Add the inner shadow with darker color + PushDraw(^{ + CGContextSetBlendMode(context, kCGBlendModeMultiply); + DrawInnerShadow(path, ScaleColorBrightness(baseColor, 0.3), CGSizeMake(0, -2), 2); + }); + + // Stroke with reversed gray gradient + PushDraw(^{ + [path clipToStroke:6]; + [path.inverse addClip]; + Gradient *grayGradient = [Gradient gradientFrom:WHITE_LEVEL(0.0, 1) to:WHITE_LEVEL(0.5, 1)]; + [grayGradient drawTopToBottom:dest]; + }); + + }); + }); +} + +void DrawStrokedShadowedText(NSString *string, NSString *fontFace, UIColor *baseColor, CGRect dest) +{ + // Create text path + UIBezierPath *text = BezierPathFromStringWithFontFace(string, fontFace); + FitPathToRect(text, dest); + DrawStrokedShadowedShape(text, baseColor, dest); +} + + +void DrawIndentedPath(UIBezierPath *path, UIColor *primary, CGRect rect) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (!context) COMPLAIN_AND_BAIL(@"No context to draw to", nil); + + PushDraw(^{ + CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeMultiply); + DrawInnerShadow(path, WHITE_LEVEL(0, 0.4), CGSizeMake(0, 2), 1); + }); + + DrawShadow(path, WHITE_LEVEL(1, 0.5), CGSizeMake(0, 2), 1); + BevelPath(path, WHITE_LEVEL(0, 0.4), 2, 0); + + PushDraw(^{ + [path addClip]; + CGContextSetAlpha(UIGraphicsGetCurrentContext(), 0.3); + + UIColor *secondary = ScaleColorBrightness(primary, 0.3); + Gradient *gradient = [Gradient gradientFrom:primary to:secondary]; + [gradient drawBottomToTop:path.bounds]; + }); + +} + +void DrawIndentedText(NSString *string, NSString *fontFace, UIColor *primary, CGRect rect) +{ + UIBezierPath *letterPath = BezierPathFromStringWithFontFace(string, fontFace); + // RotatePath(letterPath, RadiansFromDegrees(-15)); + FitPathToRect(letterPath, rect); + DrawIndentedPath(letterPath, primary, rect); +} + +void DrawGradientOverTexture(UIBezierPath *path, UIImage *texture, Gradient *gradient, CGFloat alpha) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!texture) COMPLAIN_AND_BAIL(@"Texture cannot be nil", nil); + if (!gradient) COMPLAIN_AND_BAIL(@"Gradient cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGRect rect = path.bounds; + PushDraw(^{ + CGContextSetAlpha(context, alpha); + [path addClip]; + PushLayerDraw(^{ + [texture drawInRect:rect]; + CGContextSetBlendMode(context, kCGBlendModeColor); + [gradient drawTopToBottom:rect]; + }); + }); +} + +void DrawBottomGlow(UIBezierPath *path, UIColor *color, CGFloat percent) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGRect rect = path.calculatedBounds; + CGPoint h1 = RectGetPointAtPercents(rect, 0.5f, 1.0f); + CGPoint h2 = RectGetPointAtPercents(rect, 0.5f, 1.0f - percent); + + Gradient *gradient = [Gradient easeInOutGradientBetween:color and:[color colorWithAlphaComponent:0.0f]]; + + PushDraw(^{ + [path addClip]; + [gradient drawFrom:h1 toPoint:h2]; + }); +} + +void DrawIconTopLight(UIBezierPath *path, CGFloat p) +{ + if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + CGFloat percent = 1.0f - p; + CGRect rect = path.bounds; + CGRect offset = rect; + offset.origin.y -= percent * offset.size.height; + offset = CGRectInset(offset, -offset.size.width * 0.3f, 0); + + UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:offset]; + Gradient *gradient = [Gradient gradientFrom:WHITE_LEVEL(1, 0.0) to: WHITE_LEVEL(1, 0.5)]; + + PushDraw(^{ + [path addClip]; + [ovalPath addClip]; + + // Draw gradient + CGPoint p1 = RectGetPointAtPercents(rect, 0.5, 0.0); + CGPoint p2 = RectGetPointAtPercents(ovalPath.bounds, 0.5, 1); + [gradient drawFrom:p1 toPoint:p2]; + }); +} + +CGSize GetQuartzContextSize() +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) return CGSizeZero; + return CGSizeMake(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context)); +} + +// Listing 7-4 +CGSize GetUIKitContextSize() +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) return CGSizeZero; + CGSize size = CGSizeMake(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context)); + CGFloat scale = [UIScreen mainScreen].scale; + return CGSizeMake(size.width / scale, size.height / scale); +} + +void ApplyMaskToContext(UIImage *mask) +{ + if (!mask) COMPLAIN_AND_BAIL(@"Mask cannot be nil", nil); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to apply mask to", nil); + + // Ensure that mask is grayscale + UIImage *gray = GrayscaleVersionOfImage(mask); + CGSize contextSize = GetUIKitContextSize(); + + // Clipping takes place in Quartz space, so flip before applying + FlipContextVertically(contextSize); + CGContextClipToMask(context, SizeMakeRect(contextSize), gray.CGImage); + FlipContextVertically(contextSize); +} + +UIImage *ApplyMaskToImage(UIImage *image, UIImage *mask) +{ + if (!image) COMPLAIN_AND_BAIL_NIL(@"Image cannot be nil", nil); + if (!mask) COMPLAIN_AND_BAIL_NIL(@"Mask cannot be nil", nil); + + UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0); + ApplyMaskToContext(mask); + [image drawInRect:SizeMakeRect(image.size)]; + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return result; +} + +UIImage *GradientImage(CGSize size, UIColor *c1, UIColor *c2) +{ + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + + Gradient *gradient = [Gradient gradientFrom:c1 to:c2]; + [gradient drawTopToBottom:SizeMakeRect(size)]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +UIImage *GradientMaskedReflectionImage(UIImage *sourceImage) +{ + UIImage *mirror = ImageMirroredVertically(sourceImage); + UIImage *gradImage = GrayscaleVersionOfImage(GradientImage(sourceImage.size, WHITE_LEVEL(1, 0.5), WHITE_LEVEL(0, 0.5))); + UIImage *masked = ApplyMaskToImage(mirror, gradImage); + return masked; +} + +// Listing 7-5 +void DrawGradientMaskedReflection(UIImage *image, CGRect rect) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + UIImage *gradient = GradientImage(rect.size, WHITE_LEVEL(1, 0.5), WHITE_LEVEL(1, 0.0)); + PushDraw(^{ + CGContextTranslateCTM(context, 0, rect.origin.y); + FlipContextVertically(rect.size); + CGContextTranslateCTM(context, 0, -rect.origin.y); + CGContextClipToMask(context, rect, gradient.CGImage); + [image drawInRect:rect]; + }); +} + + diff --git a/ios/QuartzBookPack/Geometry/BaseGeometry.h b/ios/QuartzBookPack/Geometry/BaseGeometry.h new file mode 100644 index 000000000..b52f6bea5 --- /dev/null +++ b/ios/QuartzBookPack/Geometry/BaseGeometry.h @@ -0,0 +1,70 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ +#import +#import +// Just because +#define TWO_PI (2 * M_PI) + +// Undefined point +#define NULLPOINT CGRectNull.origin +#define POINT_IS_NULL(_POINT_) CGPointEqualToPoint(_POINT_, NULLPOINT) + +// General +#define RECTSTRING(_aRect_) NSStringFromCGRect(_aRect_) +#define POINTSTRING(_aPoint_) NSStringFromCGPoint(_aPoint_) +#define SIZESTRING(_aSize_) NSStringFromCGSize(_aSize_) + +#define RECT_WITH_SIZE(_SIZE_) (CGRect){.size = _SIZE_} +#define RECT_WITH_POINT(_POINT_) (CGRect){.origin = _POINT_} + +// Conversion +CGFloat DegreesFromRadians(CGFloat radians); +CGFloat RadiansFromDegrees(CGFloat degrees); + +// Clamping +CGFloat Clamp(CGFloat a, CGFloat min, CGFloat max); +CGPoint ClampToRect(CGPoint pt, CGRect rect); + +// General Geometry +CGPoint RectGetCenter(CGRect rect); +CGFloat PointDistanceFromPoint(CGPoint p1, CGPoint p2); + +// Construction +CGRect RectMakeRect(CGPoint origin, CGSize size); +CGRect SizeMakeRect(CGSize size); +CGRect PointsMakeRect(CGPoint p1, CGPoint p2); +CGRect OriginMakeRect(CGPoint origin); +CGRect RectAroundCenter(CGPoint center, CGSize size); +CGRect RectCenteredInRect(CGRect rect, CGRect mainRect); + +// Point Locations +CGPoint RectGetPointAtPercents(CGRect rect, CGFloat xPercent, CGFloat yPercent); +CGPoint PointAddPoint(CGPoint p1, CGPoint p2); +CGPoint PointSubtractPoint(CGPoint p1, CGPoint p2); + +// Cardinal Points +CGPoint RectGetTopLeft(CGRect rect); +CGPoint RectGetTopRight(CGRect rect); +CGPoint RectGetBottomLeft(CGRect rect); +CGPoint RectGetBottomRight(CGRect rect); +CGPoint RectGetMidTop(CGRect rect); +CGPoint RectGetMidBottom(CGRect rect); +CGPoint RectGetMidLeft(CGRect rect); +CGPoint RectGetMidRight(CGRect rect); + +// Aspect and Fitting +CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor); +CGSize RectGetScale(CGRect sourceRect, CGRect destRect); +CGFloat AspectScaleFill(CGSize sourceSize, CGRect destRect); +CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect); +CGRect RectByFittingRect(CGRect sourceRect, CGRect destinationRect); +CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect); +CGRect RectInsetByPercent(CGRect rect, CGFloat percent); + +// Transforms +CGFloat TransformGetXScale(CGAffineTransform t); +CGFloat TransformGetYScale(CGAffineTransform t); +CGFloat TransformGetRotation(CGAffineTransform t); diff --git a/ios/QuartzBookPack/Geometry/BaseGeometry.m b/ios/QuartzBookPack/Geometry/BaseGeometry.m new file mode 100644 index 000000000..6e8b655d0 --- /dev/null +++ b/ios/QuartzBookPack/Geometry/BaseGeometry.m @@ -0,0 +1,242 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "BaseGeometry.h" + +#pragma mark - Conversion +// Degrees from radians +CGFloat DegreesFromRadians(CGFloat radians) +{ + return radians * 180.0f / M_PI; +} + +// Radians from degrees +CGFloat RadiansFromDegrees(CGFloat degrees) +{ + return degrees * M_PI / 180.0f; +} + +#pragma mark - Clamp +CGFloat Clamp(CGFloat a, CGFloat min, CGFloat max) +{ + return fmin(fmax(min, a), max); +} + +CGPoint ClampToRect(CGPoint pt, CGRect rect) +{ + CGFloat x = Clamp(pt.x, CGRectGetMinX(rect), CGRectGetMaxX(rect)); + CGFloat y = Clamp(pt.y, CGRectGetMinY(rect), CGRectGetMaxY(rect)); + return CGPointMake(x, y); +} + + +#pragma mark - General Geometry +CGPoint RectGetCenter(CGRect rect) +{ + return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); +} + +CGFloat PointDistanceFromPoint(CGPoint p1, CGPoint p2) +{ + CGFloat dx = p2.x - p1.x; + CGFloat dy = p2.y - p1.y; + + return sqrt(dx*dx + dy*dy); +} + +CGPoint RectGetPointAtPercents(CGRect rect, CGFloat xPercent, CGFloat yPercent) +{ + CGFloat dx = xPercent * rect.size.width; + CGFloat dy = yPercent * rect.size.height; + return CGPointMake(rect.origin.x + dx, rect.origin.y + dy); +} + +#pragma mark - Rectangle Construction +CGRect RectMakeRect(CGPoint origin, CGSize size) +{ + return (CGRect){.origin = origin, .size = size}; +} + +CGRect SizeMakeRect(CGSize size) +{ + return (CGRect){.size = size}; +} + +CGRect PointsMakeRect(CGPoint p1, CGPoint p2) +{ + CGRect rect = CGRectMake(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y); + return CGRectStandardize(rect); +} + +CGRect OriginMakeRect(CGPoint origin) +{ + return (CGRect){.origin = origin}; +} + +CGRect RectAroundCenter(CGPoint center, CGSize size) +{ + CGFloat halfWidth = size.width / 2.0f; + CGFloat halfHeight = size.height / 2.0f; + + return CGRectMake(center.x - halfWidth, center.y - halfHeight, size.width, size.height); +} + +CGRect RectCenteredInRect(CGRect rect, CGRect mainRect) +{ + CGFloat dx = CGRectGetMidX(mainRect)-CGRectGetMidX(rect); + CGFloat dy = CGRectGetMidY(mainRect)-CGRectGetMidY(rect); + return CGRectOffset(rect, dx, dy); +} + +#pragma mark - Point Location +CGPoint PointAddPoint(CGPoint p1, CGPoint p2) +{ + return CGPointMake(p1.x + p2.x, p1.y + p2.y); +} + +CGPoint PointSubtractPoint(CGPoint p1, CGPoint p2) +{ + return CGPointMake(p1.x - p2.x, p1.y - p2.y); +} + +#pragma mark - Cardinal Points +CGPoint RectGetTopLeft(CGRect rect) +{ + return CGPointMake( + CGRectGetMinX(rect), + CGRectGetMinY(rect) + ); +} + +CGPoint RectGetTopRight(CGRect rect) +{ + return CGPointMake( + CGRectGetMaxX(rect), + CGRectGetMinY(rect) + ); +} + +CGPoint RectGetBottomLeft(CGRect rect) +{ + return CGPointMake( + CGRectGetMinX(rect), + CGRectGetMaxY(rect) + ); +} + +CGPoint RectGetBottomRight(CGRect rect) +{ + return CGPointMake( + CGRectGetMaxX(rect), + CGRectGetMaxY(rect) + ); +} + +CGPoint RectGetMidTop(CGRect rect) +{ + return CGPointMake( + CGRectGetMidX(rect), + CGRectGetMinY(rect) + ); +} + +CGPoint RectGetMidBottom(CGRect rect) +{ + return CGPointMake( + CGRectGetMidX(rect), + CGRectGetMaxY(rect) + ); +} + +CGPoint RectGetMidLeft(CGRect rect) +{ + return CGPointMake( + CGRectGetMinX(rect), + CGRectGetMidY(rect) + ); +} + +CGPoint RectGetMidRight(CGRect rect) +{ + return CGPointMake( + CGRectGetMaxX(rect), + CGRectGetMidY(rect) + ); +} + +#pragma mark - Aspect and Fitting +CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor) +{ + return CGSizeMake(aSize.width * factor, aSize.height * factor); +} + +CGSize RectGetScale(CGRect sourceRect, CGRect destRect) +{ + CGSize sourceSize = sourceRect.size; + CGSize destSize = destRect.size; + + CGFloat scaleW = destSize.width / sourceSize.width; + CGFloat scaleH = destSize.height / sourceSize.height; + + return CGSizeMake(scaleW, scaleH); +} + +CGFloat AspectScaleFill(CGSize sourceSize, CGRect destRect) +{ + CGSize destSize = destRect.size; + CGFloat scaleW = destSize.width / sourceSize.width; + CGFloat scaleH = destSize.height / sourceSize.height; + return fmax(scaleW, scaleH); +} + +CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect) +{ + CGSize destSize = destRect.size; + CGFloat scaleW = destSize.width / sourceSize.width; + CGFloat scaleH = destSize.height / sourceSize.height; + return fmin(scaleW, scaleH); +} + +CGRect RectByFittingRect(CGRect sourceRect, CGRect destinationRect) +{ + CGFloat aspect = AspectScaleFit(sourceRect.size, destinationRect); + CGSize targetSize = SizeScaleByFactor(sourceRect.size, aspect); + return RectAroundCenter(RectGetCenter(destinationRect), targetSize); +} + +CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect) +{ + CGFloat aspect = AspectScaleFill(sourceRect.size, destinationRect); + CGSize targetSize = SizeScaleByFactor(sourceRect.size, aspect); + return RectAroundCenter(RectGetCenter(destinationRect), targetSize); +} + +CGRect RectInsetByPercent(CGRect rect, CGFloat percent) +{ + CGFloat wInset = rect.size.width * (percent / 2.0f); + CGFloat hInset = rect.size.height * (percent / 2.0f); + return CGRectInset(rect, wInset, hInset); +} + +#pragma mark - Transforms + +// Extract the x scale from transform +CGFloat TransformGetXScale(CGAffineTransform t) +{ + return sqrt(t.a * t.a + t.c * t.c); +} + +// Extract the y scale from transform +CGFloat TransformGetYScale(CGAffineTransform t) +{ + return sqrt(t.b * t.b + t.d * t.d); +} + +// Extract the rotation in radians +CGFloat TransformGetRotation(CGAffineTransform t) +{ + return atan2f(t.b, t.a); +} diff --git a/ios/QuartzBookPack/Image/ImageUtils.h b/ios/QuartzBookPack/Image/ImageUtils.h new file mode 100644 index 000000000..f70ab1eb2 --- /dev/null +++ b/ios/QuartzBookPack/Image/ImageUtils.h @@ -0,0 +1,40 @@ +/* + + Erica Sadun, http://ericasadun.com + + Gathered for book examples + + */ + +#import +#import + +// 4 bytes per ARGB pixel, 8 bits per byte +#define ARGB_COUNT 4 +#define BITS_PER_COMPONENT 8 + +UIEdgeInsets BuildInsets(CGRect alignmentRect, CGRect imageBounds); + +UIImage *BuildSwatchWithColor(UIColor *color, CGFloat side); +UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting); +UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect); +UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect); + +UIImage *GrayscaleVersionOfImage(UIImage *sourceImage); +UIImage *InvertImage(UIImage *sourceImage); + +NSData *BytesFromRGBImage(UIImage *sourceImage); +UIImage *ImageFromRGBBytes(NSData *data, CGSize targetSize); + +void FlipContextVertically(CGSize size); +void FlipContextHorizontally(CGSize size); +void FlipImageContextVertically(); +void FlipImageContextHorizontally(); +void RotateContext(CGSize size, CGFloat theta); +void MoveContextByVector(CGPoint vector); + +UIImage *ImageMirroredVertically(UIImage *image); + +void DrawPDFPageInRect(CGPDFPageRef pageRef, CGRect destinationRect); + +UIImage *GaussianBlurImage(UIImage *image, CGFloat radius); diff --git a/ios/QuartzBookPack/Image/ImageUtils.m b/ios/QuartzBookPack/Image/ImageUtils.m new file mode 100644 index 000000000..d108fd7db --- /dev/null +++ b/ios/QuartzBookPack/Image/ImageUtils.m @@ -0,0 +1,372 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "ImageUtils.h" +#import "Utility.h" +#import "BaseGeometry.h" + +// Chapter 3-8 +// Establish insets for image alignment +UIEdgeInsets BuildInsets(CGRect alignmentRect, CGRect imageBounds) +{ + // Ensure alignment rect is fully within source + CGRect targetRect = CGRectIntersection(alignmentRect, imageBounds); + + // Calculate insets + UIEdgeInsets insets; + insets.left = CGRectGetMinX(targetRect) - CGRectGetMinX(imageBounds); + insets.right = CGRectGetMaxX(imageBounds) - CGRectGetMaxX(targetRect); + insets.top = CGRectGetMinY(targetRect) - CGRectGetMinY(imageBounds); + insets.bottom = CGRectGetMaxY(imageBounds) - CGRectGetMaxY(targetRect); + + return insets; +} + + +// Chapter 3 - 1 +UIImage *BuildSwatchWithColor(UIColor *color, CGFloat side) +{ + // Create image context + UIGraphicsBeginImageContextWithOptions( + CGSizeMake(side, side), YES, + 0.0); + + // Perform drawing + [color setFill]; + UIRectFill(CGRectMake(0, 0, side, side)); + + // Retrieve image + UIImage *image = + UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +// Chapter 3 - 2 +UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting) +{ + CGRect targetRect = SizeMakeRect(targetSize); + UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0); + + CGRect naturalRect = (CGRect){.size = sourceImage.size}; + CGRect destinationRect = useFitting ? RectByFittingRect(naturalRect, targetRect) : RectByFillingRect(naturalRect, targetRect); + [sourceImage drawInRect:destinationRect]; + + UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return thumbnail; +} + +// Chapter 3 - 3 +UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect) +{ + // Extract image + CGImageRef imageRef = CGImageCreateWithImageInRect(sourceImage.CGImage, subRect); + if (imageRef != NULL) + { + UIImage *output = [UIImage imageWithCGImage:imageRef]; + CGImageRelease(imageRef); + return output; + } + + NSLog(@"Error: Unable to extract subimage"); + return nil; +} + +// This is a little less flaky when moving to and from Retina images +UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect) +{ + UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1); + CGRect destRect = CGRectMake(-rect.origin.x, -rect.origin.y, + sourceImage.size.width, sourceImage.size.height); + [sourceImage drawInRect:destRect]; + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} + +// Chapter 3 - 4 +UIImage *GrayscaleVersionOfImage(UIImage *sourceImage) +{ + // Establish grayscale color space + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + if (colorSpace == NULL) + { + NSLog(@"Error: Could not establish grayscale color space"); + return nil; + } + + // Extents are integers + int width = sourceImage.size.width; + int height = sourceImage.size.height; + + // Build context: one byte per pixel, no alpha + CGContextRef context = CGBitmapContextCreate(NULL, width, height, BITS_PER_COMPONENT, width, colorSpace, (CGBitmapInfo)kCGImageAlphaNone); + CGColorSpaceRelease(colorSpace); + if (context == NULL) + { + NSLog(@"Error: Could not build grayscale bitmap context"); + return nil; + } + + // Replicate image using new color space + CGRect rect = SizeMakeRect(sourceImage.size); + CGContextDrawImage(context, rect, sourceImage.CGImage); + CGImageRef imageRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + // Return the grayscale image + UIImage *output = [UIImage imageWithCGImage:imageRef]; + CFRelease(imageRef); + return output; +} + +// Just for fun. Return image with colors flipped +UIImage *InvertImage(UIImage *sourceImage) +{ + UIGraphicsBeginImageContextWithOptions(sourceImage.size, NO, 0.0); + CGContextRef context = UIGraphicsGetCurrentContext(); + [sourceImage drawInRect:SizeMakeRect(sourceImage.size)]; + CGContextSetBlendMode(context, kCGBlendModeDifference); + CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor); + CGContextFillRect(context, SizeMakeRect(sourceImage.size)); + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return result; +} + +// Chapter 3-6 +// Extract bytes +NSData *BytesFromRGBImage(UIImage *sourceImage) +{ + if (!sourceImage) return nil; + + // Establish color space + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + if (colorSpace == NULL) + { + NSLog(@"Error creating RGB color space"); + return nil; + } + + // Establish context + int width = sourceImage.size.width; + int height = sourceImage.size.height; + CGContextRef context = CGBitmapContextCreate(NULL, width, height, BITS_PER_COMPONENT, width * ARGB_COUNT, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(colorSpace ); + if (context == NULL) + { + NSLog(@"Error creating context"); + return nil; + } + + // Draw source into context bytes + CGRect rect = (CGRect){.size = sourceImage.size}; + CGContextDrawImage(context, rect, sourceImage.CGImage); + + // Create NSData from bytes + NSData *data = [NSData dataWithBytes:CGBitmapContextGetData(context) length:(width * height * 4)]; + CGContextRelease(context); + + return data; +} + +// Chapter 3-7 +// Create image from bytes +UIImage *ImageFromRGBBytes(NSData *data, CGSize targetSize) +{ + // Check data + int width = targetSize.width; + int height = targetSize.height; + if (data.length < (width * height * 4)) + { + NSLog(@"Error: Not enough RGB data provided. Got %d bytes. Expected %d bytes", data.length, width * height * 4); + return nil; + } + + // Create a color space + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + if (colorSpace == NULL) + { + NSLog(@"Error creating RGB colorspace"); + return nil; + } + + // Create the bitmap context + Byte *bytes = (Byte *) data.bytes; + CGContextRef context = CGBitmapContextCreate(bytes, width, height, BITS_PER_COMPONENT, width * ARGB_COUNT, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(colorSpace ); + if (context == NULL) + { + NSLog(@"Error. Could not create context"); + return nil; + } + + // Convert to image + CGImageRef imageRef = CGBitmapContextCreateImage(context); + UIImage *image = [UIImage imageWithCGImage:imageRef]; + + // Clean up + CGContextRelease(context); + CFRelease(imageRef); + + return image; +} + +#pragma mark - Context + +// From Chapter 1 +void FlipContextVertically(CGSize size) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) + { + NSLog(@"Error: No context to flip"); + return; + } + + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformScale(transform, 1.0f, -1.0f); + transform = CGAffineTransformTranslate(transform, 0.0f, -size.height); + CGContextConcatCTM(context, transform); +} + +void FlipImageContextVertically() +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) + { + NSLog(@"Error: No context to flip"); + return; + } + + // I don't like this approach + // CGFloat scale = [UIScreen mainScreen].scale; + // CGSize size = CGSizeMake(CGBitmapContextGetWidth(context) / scale, CGBitmapContextGetHeight(context) / scale); + // FlipContextVertically(size); + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + FlipContextVertically(image.size); +} + +void FlipContextHorizontally(CGSize size) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformScale(transform, -1.0f, 1.0f); + transform = CGAffineTransformTranslate(transform, -size.width, 0.0); + CGContextConcatCTM(context, transform); +} + +void FlipImageContextHorizontally() +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) + { + NSLog(@"Error: No context to flip"); + return; + } + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + FlipContextHorizontally(image.size); +} + +void RotateContext(CGSize size, CGFloat theta) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(context, size.width / 2.0f, size.height / 2.0f); + CGContextRotateCTM(context, theta); + CGContextTranslateCTM(context, -size.width / 2.0f, -size.height / 2.0f); +} + +void MoveContextByVector(CGPoint vector) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(context, vector.x, vector.y); +} + +UIImage *ImageMirroredVertically(UIImage *source) +{ + UIGraphicsBeginImageContextWithOptions(source.size, NO, 0.0); + FlipContextVertically(source.size); + [source drawInRect:SizeMakeRect(source.size)]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + + +#pragma mark - PDF Util +// Chapter 3-12 +void DrawPDFPageInRect(CGPDFPageRef pageRef, CGRect destinationRect) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) + { + NSLog(@"Error: No context to draw to"); + return; + } + + CGContextSaveGState(context); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + + // Flip the context to Quartz space + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformScale(transform, 1.0f, -1.0f); + transform = CGAffineTransformTranslate(transform, 0.0f, -image.size.height); + CGContextConcatCTM(context, transform); + + // Flip the rect, which remains in UIKit space + CGRect d = CGRectApplyAffineTransform(destinationRect, transform); + + // Calculate a rectangle to draw to + CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox); + CGFloat drawingAspect = AspectScaleFit(pageRect.size, d); + CGRect drawingRect = RectByFittingRect(pageRect, d); + + // Draw the page outline (optional) + UIRectFrame(drawingRect); + + // Adjust the context + CGContextTranslateCTM(context, drawingRect.origin.x, drawingRect.origin.y); + CGContextScaleCTM(context, drawingAspect, drawingAspect); + + // Draw the page + CGContextDrawPDFPage(context, pageRef); + CGContextRestoreGState(context); +} + +#pragma mark - Masking, Blurring +// Listing 7-3 +UIImage *GaussianBlurImage(UIImage *image, CGFloat radius) +{ + if (!image) COMPLAIN_AND_BAIL_NIL(@"Mask cannot be nil", nil); + + CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; + [blurFilter setValue: [CIImage imageWithCGImage:image.CGImage] + forKey: @"inputImage"]; + [blurFilter setValue:@(radius) forKey:@"inputRadius"]; + + CIFilter *crop = [CIFilter filterWithName: @"CICrop"]; + [crop setDefaults]; + [crop setValue:blurFilter.outputImage forKey:@"inputImage"]; + + CGFloat scale = [[UIScreen mainScreen] scale]; + CGFloat w = image.size.width * scale; + CGFloat h = image.size.height * scale; + CIVector *v = [CIVector vectorWithX:0 Y:0 Z:w W:h]; + [crop setValue:v forKey:@"inputRectangle"]; + + CGImageRef cgImageRef = [[CIContext contextWithOptions:nil] createCGImage:crop.outputImage fromRect:crop.outputImage.extent]; + + UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0); + FlipContextVertically(image.size); + CGContextDrawImage(UIGraphicsGetCurrentContext(), SizeMakeRect(image.size), cgImageRef); + UIImage *blurred = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return blurred; +} \ No newline at end of file diff --git a/ios/QuartzBookPack/TextDrawing/Drawing-Text.h b/ios/QuartzBookPack/TextDrawing/Drawing-Text.h new file mode 100644 index 000000000..00b4fae92 --- /dev/null +++ b/ios/QuartzBookPack/TextDrawing/Drawing-Text.h @@ -0,0 +1,23 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import +@import Foundation; +@import CoreText; + +#import "Drawing-Text.h" + +// Sizing +NSArray *WidthArrayForStringWithFont(NSString *string, UIFont *font); + +// Drawing +void DrawStringInRect(NSString *string, CGRect rect, UIFont *font, NSTextAlignment alignment, UIColor *color); +void DrawWrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color); +void DrawUnwrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color); +void DrawStringCenteredInRect(NSString *string, UIFont *font, CGRect rect); + +UIFont *FontForWrappedString(NSString *string, NSString *fontFace, CGRect rect, CGFloat tolerance); diff --git a/ios/QuartzBookPack/TextDrawing/Drawing-Text.m b/ios/QuartzBookPack/TextDrawing/Drawing-Text.m new file mode 100644 index 000000000..46599674c --- /dev/null +++ b/ios/QuartzBookPack/TextDrawing/Drawing-Text.m @@ -0,0 +1,175 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import "Drawing-Text.h" +#import "Utility.h" + +#pragma mark - Drawing + +void DrawStringInRect(NSString *string, CGRect rect, UIFont *font, NSTextAlignment alignment, UIColor *color) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) return; + + NSRange range = NSMakeRange(0, string.length); + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; + + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + style.alignment = alignment; + style.lineBreakMode = NSLineBreakByWordWrapping; + attributes[NSFontAttributeName] = font; + attributes[NSForegroundColorAttributeName] = color; + attributes[NSParagraphStyleAttributeName] = style; + [attributedString addAttributes:attributes range:range]; + + CGRect destRect = [string boundingRectWithSize:CGSizeMake(rect.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; + CGRect outputRect = RectCenteredInRect(destRect, rect); + [attributedString drawInRect:outputRect]; +} + +#pragma mark - Unwrapped +UIFont *FontForUnwrappedString(NSString *string, NSString *fontFace, CGRect rect) +{ + CGFloat fontSize = 1; + UIFont *font = [UIFont fontWithName:fontFace size:fontSize]; + CGSize destSize = [string sizeWithAttributes:@{NSFontAttributeName:font}]; + + while ((destSize.width < rect.size.width) && (destSize.height < rect.size.height)) + { + fontSize++; + UIFont *proposedFont = [UIFont fontWithName:fontFace size:fontSize]; + destSize = [string sizeWithAttributes:@{NSFontAttributeName:proposedFont}]; + if ((destSize.height > rect.size.height) || (destSize.width > rect.size.width)) + return font; + + font = proposedFont; + } + + return font; +} + +void DrawUnwrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color) +{ + UIFont *font = FontForUnwrappedString(string, fontFace, rect); + DrawStringInRect(string, rect, font, alignment, color); +} + +void DrawStringCenteredInRect(NSString *string, UIFont *font, CGRect rect) +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + // Calculate string size + CGSize stringSize = [string sizeWithAttributes:@{NSFontAttributeName:font}]; + + // Find the target rectangle + CGRect target = RectAroundCenter(RectGetCenter(rect), stringSize); + + // Draw the string + [string drawInRect:target withAttributes:@{NSFontAttributeName:font}]; +} + + +#pragma mark - Wrapping + +UIFont *FontForWrappedString(NSString *string, NSString *fontFace, CGRect rect, CGFloat tolerance) +{ + if (rect.size.height < 1.0f) return nil; + + CGFloat adjustedWidth = tolerance * rect.size.width; + CGSize measureSize = CGSizeMake(adjustedWidth, CGFLOAT_MAX); + + // Initialize the proposed font + CGFloat fontSize = 1; + UIFont *proposedFont = [UIFont fontWithName:fontFace size:fontSize]; + + NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; + + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + attributes[NSParagraphStyleAttributeName] = paragraphStyle; + attributes[NSFontAttributeName] = proposedFont; + + // Measure the target + CGSize targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; + + // Double until the size is exceeded + while (targetSize.height <= rect.size.height) + { + // Establish a new proposed font + fontSize *= 2; + proposedFont = [UIFont fontWithName:fontFace size:fontSize]; + + // Measure the target + attributes[NSFontAttributeName] = proposedFont; + targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; + + // Break when the calculated height is too much + if (targetSize.height > rect.size.height) + break; + } + + // Search between the previous and current font sizes + CGFloat minFontSize = fontSize / 2; + CGFloat maxFontSize = fontSize; + while (1) + { + // Get the midpoint between the two + CGFloat midPoint = (minFontSize + (maxFontSize - minFontSize) / 2); + proposedFont = [UIFont fontWithName:fontFace size:midPoint]; + attributes[NSFontAttributeName] = proposedFont; + targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; + + // Look up one font size + UIFont *nextFont = [UIFont fontWithName:fontFace size:midPoint + 1]; + attributes[NSFontAttributeName] = nextFont; + CGSize nextTargetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;; + + // Test both fonts + CGFloat tooBig = targetSize.height > rect.size.height; + CGFloat nextIsTooBig = nextTargetSize.height > rect.size.height; + + // If the current is sized right but the next is too big, win + if (!tooBig && nextIsTooBig) + return [UIFont fontWithName:fontFace size:midPoint]; + + // Adjust the search space + if (tooBig) + maxFontSize = midPoint; + else + minFontSize = midPoint; + } + + // Should never get here + return [UIFont fontWithName:fontFace size:fontSize / 2]; +} + +CGSize AttributedStringSize(NSAttributedString *string, CGSize constrainedSize) +{ + return [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; +} + +void DrawWrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color) +{ + + UIFont *font = FontForWrappedString(string, fontFace, rect, 0.95); + + NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString:string]; + NSRange r = NSMakeRange(0, string.length); + [s addAttribute:NSFontAttributeName value:font range:r]; + [s addAttribute:NSForegroundColorAttributeName value:color range:r]; + NSMutableParagraphStyle *p = [NSParagraphStyle defaultParagraphStyle].mutableCopy; + p.hyphenationFactor = 0.25f; + p.alignment = alignment; + [s addAttribute:NSParagraphStyleAttributeName value:p range:r]; + + CGRect stringBounds = SizeMakeRect(AttributedStringSize(s, rect.size)); + stringBounds.size.width = fminf(rect.size.width, stringBounds.size.width); + CGRect dest = RectCenteredInRect(stringBounds, rect); + dest.size.height = CGFLOAT_MAX; + [s drawInRect:dest]; +} \ No newline at end of file diff --git a/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h b/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h new file mode 100644 index 000000000..6fa0befdb --- /dev/null +++ b/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h @@ -0,0 +1,13 @@ +/* + Erica Sadun, http://ericasadun.com + iPhone Developer's Cookbook, 6.x Edition + BSD License, Use at your own risk + */ + +#import +#import + +#import "UIBezierPath+Elements.h" +@interface UIBezierPath (TextUtilities) +- (void) drawAttributedString: (NSAttributedString *) string; +@end diff --git a/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m b/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m new file mode 100644 index 000000000..9d893e664 --- /dev/null +++ b/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m @@ -0,0 +1,56 @@ +/* + Erica Sadun, http://ericasadun.com + iPhone Developer's Cookbook, 6.x Edition + BSD License, Use at your own risk + */ + +#import "UIBezierPath+Text.h" +#import "Utility.h" + +@implementation UIBezierPath (TextUtilities) +- (void) drawAttributedString: (NSAttributedString *) string +{ + if (!string) return; + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); + + if (self.elements.count < 2) return; + + // Keep a running tab of how far the glyphs have traveled to + // be able to calculate the percent along the point path + float glyphDistance = 0.0f; + float lineLength = self.pathLength; + + for (int loc = 0; loc < string.length; loc++) + { + // Retrieve item + NSRange range = NSMakeRange(loc, 1); + NSAttributedString *item = [string attributedSubstringFromRange:range]; + + // Start halfway through each glyph + CGRect bounding = [item boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 context:nil]; + glyphDistance += bounding.size.width / 2; + + // Find point + CGPoint slope; + CGFloat percentConsumed = glyphDistance / lineLength; + CGPoint targetPoint = [self pointAtPercent:percentConsumed withSlope:&slope]; + + // Accommodate the forward progress + glyphDistance += bounding.size.width / 2; + if (percentConsumed >= 1.0f) break; + + // Calculate the rotation + float angle = atan(slope.y / slope.x); // + M_PI; + if (slope.x < 0) angle += M_PI; // going left, update the angle + + // Draw the glyph + PushDraw(^{ + CGContextTranslateCTM(context, targetPoint.x, targetPoint.y); + CGContextRotateCTM(context, angle); + CGContextTranslateCTM(context, -bounding.size.width / 2, -item.size.height / 2); + [item drawAtPoint:CGPointZero]; + }); + } +} +@end diff --git a/ios/QuartzBookPack/Utility/Utility.h b/ios/QuartzBookPack/Utility/Utility.h new file mode 100644 index 000000000..3ad23f585 --- /dev/null +++ b/ios/QuartzBookPack/Utility/Utility.h @@ -0,0 +1,48 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + +#import +#import + +#import "BaseGeometry.h" +#import "Drawing-Block.h" +#import "Drawing-Util.h" +#import "Drawing-Gradient.h" +#import "Bezier.h" +#import "ImageUtils.h" +#import "UIBezierPath+Text.h" +#import "Drawing-Text.h" + +#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] initWithTitle:TITLE style:UIBarButtonItemStylePlain target:self action:SELECTOR] +#define RGBCOLOR(_R_, _G_, _B_) [UIColor colorWithRed:(CGFloat)(_R_)/255.0f green: (CGFloat)(_G_)/255.0f blue: (CGFloat)(_B_)/255.0f alpha: 1.0f] + +#define OLIVE RGBCOLOR(125, 162, 63) +#define LIGHTPURPLE RGBCOLOR(99, 62, 162) +#define DARKGREEN RGBCOLOR(40, 55, 32) + +// Bail with complaint +#define COMPLAIN_AND_BAIL(_COMPLAINT_, _ARG_) {NSLog(_COMPLAINT_, _ARG_); return;} +#define COMPLAIN_AND_BAIL_NIL(_COMPLAINT_, _ARG_) {NSLog(_COMPLAINT_, _ARG_); return nil;} + +#define SEED_RANDOM {static BOOL seeded = NO; if (!seeded) {seeded = YES; srandom((unsigned int) time(0));}} +#define RANDOM(_X_) (NSInteger)(random() % _X_) +#define RANDOM_01 ((double) random() / (double) LONG_MAX) +#define RANDOM_BOOL (BOOL)((NSInteger)random() % 2) +#define RANDOM_PT(_RECT_) CGPointMake(_RECT_.origin.x + RANDOM_01 * _RECT_.size.width, _RECT_.origin.y + RANDOM_01 * _RECT_.size.height) + +UIBezierPath *BuildBunnyPath(); +UIBezierPath *BuildMoofPath(); +UIBezierPath *BuildStarPath(); + +UIColor *NoiseColor(); + +@interface NSString (Utility) ++ (NSString *) lorem: (NSUInteger) numberOfParagraphs; ++ (NSString *) loremWords: (NSUInteger) numberOfWords; +@end + +#define DEBUG_IMAGE(_IMAGE_, _NAME_) [UIImagePNGRepresentation(_IMAGE_) writeToFile:[NSString stringWithFormat:@"/Users/ericasadun/Desktop/%@.png", _NAME_] atomically:YES] + diff --git a/ios/QuartzBookPack/Utility/Utility.m b/ios/QuartzBookPack/Utility/Utility.m new file mode 100644 index 000000000..dacb2cc18 --- /dev/null +++ b/ios/QuartzBookPack/Utility/Utility.m @@ -0,0 +1,240 @@ +#import "Utility.h" + +typedef BOOL (^TestingBlock)(id object); +@interface NSArray (Utility) +@end + +@implementation NSArray (Utility) +- (NSArray *) collect: (TestingBlock) aBlock +{ + NSMutableArray *resultArray = [NSMutableArray array]; + for (id object in self) + { + BOOL result = aBlock(object); + if (result) + [resultArray addObject:object]; + } + return resultArray; +} +@end + +@implementation NSString (Utility) ++ (NSString *) ipsum:(NSUInteger) numberOfParagraphs +{ + NSString *urlString = [NSString stringWithFormat:@"http://loripsum.net/api/%0d/short/prude/plaintext", numberOfParagraphs]; + + NSError *error; + NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString] encoding:NSUTF8StringEncoding error:&error]; + if (!string) + { + NSLog(@"Error: %@", error.localizedDescription); + return nil; + } + return string; +} + ++ (NSString *) lorem:(NSUInteger) numberOfParagraphs +{ + return [self ipsum:numberOfParagraphs]; +} + +- (NSArray *) words +{ + NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + TestingBlock block = ^BOOL(id word){return [(NSString *)word length] > 0;}; + return [words collect:block]; +} + +- (NSString *) wordRange: (NSRange) range +{ + NSArray *componentWords = self.words; + NSInteger start = range.location; + NSInteger end = range.location + range.length; + if ((start >= componentWords.count) || (end >= componentWords.count)) + return nil; + + NSArray *subArray = [componentWords subarrayWithRange:range]; + return [subArray componentsJoinedByString:@" "]; +} + ++ (NSString *) loremWords: (NSUInteger) numberOfWords +{ + // meant for up to 10 words + NSUInteger nWords = MIN(numberOfWords, 10); + NSRange r = NSMakeRange(0, nWords); + return [[NSString lorem:3] wordRange:r]; +} +@end + +UIBezierPath *BuildBunnyPath() +{ + UIBezierPath *shapePath = [UIBezierPath bezierPath]; + [shapePath moveToPoint: CGPointMake(392.05, 150.53)]; + [shapePath addCurveToPoint: CGPointMake(343.11, 129.56) controlPoint1: CGPointMake(379.34, 133.37) controlPoint2: CGPointMake(359, 133.37)]; + [shapePath addCurveToPoint: CGPointMake(305.61, 71.72) controlPoint1: CGPointMake(341.2, 119.39) controlPoint2: CGPointMake(316.41, 71.08)]; + [shapePath addCurveToPoint: CGPointMake(304.34, 100.96) controlPoint1: CGPointMake(294.8, 72.35) controlPoint2: CGPointMake(302.43, 79.35)]; + [shapePath addCurveToPoint: CGPointMake(283.36, 84.43) controlPoint1: CGPointMake(299.25, 95.24) controlPoint2: CGPointMake(287.81, 73.63)]; + [shapePath addCurveToPoint: CGPointMake(301.79, 154.99) controlPoint1: CGPointMake(272.42, 111.01) controlPoint2: CGPointMake(302.43, 148.63)]; + [shapePath addCurveToPoint: CGPointMake(287.17, 191.85) controlPoint1: CGPointMake(301.16, 161.34) controlPoint2: CGPointMake(299, 186.78)]; + [shapePath addCurveToPoint: CGPointMake(194.38, 221.09) controlPoint1: CGPointMake(282.72, 193.76) controlPoint2: CGPointMake(240.78, 195.66)]; + [shapePath addCurveToPoint: CGPointMake(128.27, 320.25) controlPoint1: CGPointMake(147.97, 246.51) controlPoint2: CGPointMake(138.44, 282.11)]; + [shapePath addCurveToPoint: CGPointMake(124.75, 348.92) controlPoint1: CGPointMake(125.53, 330.51) controlPoint2: CGPointMake(124.54, 340.12)]; + [shapePath addCurveToPoint: CGPointMake(118.34, 358.13) controlPoint1: CGPointMake(122.92, 350.31) controlPoint2: CGPointMake(120.74, 353.02)]; + [shapePath addCurveToPoint: CGPointMake(136.06, 388.68) controlPoint1: CGPointMake(112.42, 370.73) controlPoint2: CGPointMake(123.06, 383.91)]; + [shapePath addCurveToPoint: CGPointMake(144.8, 399.06) controlPoint1: CGPointMake(138.8, 392.96) controlPoint2: CGPointMake(141.79, 396.49)]; + [shapePath addCurveToPoint: CGPointMake(210.9, 408.6) controlPoint1: CGPointMake(158.14, 410.5) controlPoint2: CGPointMake(205.18, 406.69)]; + [shapePath addCurveToPoint: CGPointMake(240.78, 417.5) controlPoint1: CGPointMake(216.62, 410.5) controlPoint2: CGPointMake(234.41, 417.5)]; + [shapePath addCurveToPoint: CGPointMake(267.47, 411.78) controlPoint1: CGPointMake(247.13, 417.5) controlPoint2: CGPointMake(267.47, 419.4)]; + [shapePath addCurveToPoint: CGPointMake(250.3, 385.71) controlPoint1: CGPointMake(267.47, 394.61) controlPoint2: CGPointMake(250.3, 385.71)]; + [shapePath addCurveToPoint: CGPointMake(274.46, 371.73) controlPoint1: CGPointMake(250.3, 385.71) controlPoint2: CGPointMake(260.48, 379.99)]; + [shapePath addCurveToPoint: CGPointMake(302.43, 350.12) controlPoint1: CGPointMake(288.45, 363.47) controlPoint2: CGPointMake(297.34, 350.76)]; + [shapePath addCurveToPoint: CGPointMake(318.32, 389.53) controlPoint1: CGPointMake(312.22, 348.89) controlPoint2: CGPointMake(311.33, 381.9)]; + [shapePath addCurveToPoint: CGPointMake(341.84, 421.94) controlPoint1: CGPointMake(325.32, 397.15) controlPoint2: CGPointMake(332.15, 418.51)]; + [shapePath addCurveToPoint: CGPointMake(375.53, 413.68) controlPoint1: CGPointMake(349.08, 424.51) controlPoint2: CGPointMake(373.62, 421.31)]; + [shapePath addCurveToPoint: CGPointMake(367.26, 398.43) controlPoint1: CGPointMake(377.43, 406.06) controlPoint2: CGPointMake(372.35, 405.42)]; + [shapePath addCurveToPoint: CGPointMake(361.54, 352.66) controlPoint1: CGPointMake(362.18, 391.43) controlPoint2: CGPointMake(356.46, 363.47)]; + [shapePath addCurveToPoint: CGPointMake(378.07, 277.66) controlPoint1: CGPointMake(366.63, 341.86) controlPoint2: CGPointMake(376.8, 296.73)]; + [shapePath addCurveToPoint: CGPointMake(388.87, 220.45) controlPoint1: CGPointMake(379.34, 258.59) controlPoint2: CGPointMake(378.7, 245.88)]; + [shapePath addCurveToPoint: CGPointMake(411.12, 189.31) controlPoint1: CGPointMake(405.4, 214.1) controlPoint2: CGPointMake(410.48, 197.57)]; + [shapePath addCurveToPoint: CGPointMake(392.05, 150.53) controlPoint1: CGPointMake(411.76, 181.04) controlPoint2: CGPointMake(404.76, 167.7)]; + [shapePath closePath]; + return shapePath; +} + +// http://pastie.org/private/7i0lgpfqtlkihbjyn0ba +// http://imgur.com/Po3uoIy +UIBezierPath *BuildMoofPath() +{ + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint: CGPointMake(107, 66)]; + [path addCurveToPoint: CGPointMake(119, 69) controlPoint1: CGPointMake(113.17, 65.65) controlPoint2: CGPointMake(116.61, 65.38)]; + [path addCurveToPoint: CGPointMake(118, 100) controlPoint1: CGPointMake(118.67, 79.33) controlPoint2: CGPointMake(118.33, 89.67)]; + [path addCurveToPoint: CGPointMake(140, 114) controlPoint1: CGPointMake(125.33, 104.67) controlPoint2: CGPointMake(132.67, 109.33)]; + [path addCurveToPoint: CGPointMake(240, 105) controlPoint1: CGPointMake(169.22, 124.94) controlPoint2: CGPointMake(219.91, 117.23)]; + [path addCurveToPoint: CGPointMake(261, 76) controlPoint1: CGPointMake(252.29, 97.52) controlPoint2: CGPointMake(251.99, 86.39)]; + [path addCurveToPoint: CGPointMake(267, 78) controlPoint1: CGPointMake(263, 76.67) controlPoint2: CGPointMake(265, 77.33)]; + [path addCurveToPoint: CGPointMake(255, 145) controlPoint1: CGPointMake(276.45, 112.7) controlPoint2: CGPointMake(260.16, 119.11)]; + [path addCurveToPoint: CGPointMake(262, 188) controlPoint1: CGPointMake(257.33, 159.33) controlPoint2: CGPointMake(259.67, 173.67)]; + [path addCurveToPoint: CGPointMake(208, 232) controlPoint1: CGPointMake(268.07, 224.31) controlPoint2: CGPointMake(240.23, 239.3)]; + [path addCurveToPoint: CGPointMake(206, 225) controlPoint1: CGPointMake(207.33, 229.67) controlPoint2: CGPointMake(206.67, 227.33)]; + [path addCurveToPoint: CGPointMake(220, 192) controlPoint1: CGPointMake(217.21, 215.24) controlPoint2: CGPointMake(226.39, 211.63)]; + [path addCurveToPoint: CGPointMake(127, 186) controlPoint1: CGPointMake(197.32, 181.14) controlPoint2: CGPointMake(155.61, 179.26)]; + [path addCurveToPoint: CGPointMake(78, 230) controlPoint1: CGPointMake(126.93, 214.62) controlPoint2: CGPointMake(110.79, 250.16)]; + [path addCurveToPoint: CGPointMake(86, 203) controlPoint1: CGPointMake(76.79, 220.05) controlPoint2: CGPointMake(83.57, 212.84)]; + [path addCurveToPoint: CGPointMake(85, 132) controlPoint1: CGPointMake(90.93, 183.08) controlPoint2: CGPointMake(90.79, 150.81)]; + [path addCurveToPoint: CGPointMake(28, 121) controlPoint1: CGPointMake(60.95, 132.68) controlPoint2: CGPointMake(39.36, 132.88)]; + [path addCurveToPoint: CGPointMake(57, 91) controlPoint1: CGPointMake(27.34, 95.81) controlPoint2: CGPointMake(48.07, 104.89)]; + [path addCurveToPoint: CGPointMake(60, 73) controlPoint1: CGPointMake(58, 85) controlPoint2: CGPointMake(59, 79)]; + [path addCurveToPoint: CGPointMake(85, 76) controlPoint1: CGPointMake(66.31, 61.96) controlPoint2: CGPointMake(80.38, 68.36)]; + [path addCurveToPoint: CGPointMake(107, 66) controlPoint1: CGPointMake(93.79, 75.13) controlPoint2: CGPointMake(101.6, 70.61)]; + [path closePath]; + [path moveToPoint: CGPointMake(116, 69)]; + [path addCurveToPoint: CGPointMake(84, 78) controlPoint1: CGPointMake(104.12, 69.8) controlPoint2: CGPointMake(92.62, 83.34)]; + [path addCurveToPoint: CGPointMake(77, 72) controlPoint1: CGPointMake(81.67, 76) controlPoint2: CGPointMake(79.33, 74)]; + [path addCurveToPoint: CGPointMake(67, 72) controlPoint1: CGPointMake(73.67, 72) controlPoint2: CGPointMake(70.33, 72)]; + [path addCurveToPoint: CGPointMake(60, 93) controlPoint1: CGPointMake(60.55, 76.74) controlPoint2: CGPointMake(64.31, 86.1)]; + [path addCurveToPoint: CGPointMake(32, 111) controlPoint1: CGPointMake(54.17, 102.33) controlPoint2: CGPointMake(37.4, 101.45)]; + [path addCurveToPoint: CGPointMake(32, 121) controlPoint1: CGPointMake(32, 114.33) controlPoint2: CGPointMake(32, 117.67)]; + [path addCurveToPoint: CGPointMake(88, 129) controlPoint1: CGPointMake(44.53, 129.24) controlPoint2: CGPointMake(66.88, 129.21)]; + [path addCurveToPoint: CGPointMake(109, 172) controlPoint1: CGPointMake(93.2, 149.04) controlPoint2: CGPointMake(120.58, 148.18)]; + [path addCurveToPoint: CGPointMake(92, 176) controlPoint1: CGPointMake(103.33, 173.33) controlPoint2: CGPointMake(97.67, 174.67)]; + [path addCurveToPoint: CGPointMake(83, 230) controlPoint1: CGPointMake(91.91, 195.18) controlPoint2: CGPointMake(78.84, 222.44)]; + [path addCurveToPoint: CGPointMake(85, 230) controlPoint1: CGPointMake(83.67, 230) controlPoint2: CGPointMake(84.33, 230)]; + [path addCurveToPoint: CGPointMake(127, 183) controlPoint1: CGPointMake(131.62, 242.71) controlPoint2: CGPointMake(110.92, 195.36)]; + [path addCurveToPoint: CGPointMake(223, 190) controlPoint1: CGPointMake(149.67, 169.94) controlPoint2: CGPointMake(207.14, 180.38)]; + [path addCurveToPoint: CGPointMake(209, 226) controlPoint1: CGPointMake(228.64, 206.57) controlPoint2: CGPointMake(224.64, 221.84)]; + [path addCurveToPoint: CGPointMake(210, 229) controlPoint1: CGPointMake(209.33, 227) controlPoint2: CGPointMake(209.67, 228)]; + [path addCurveToPoint: CGPointMake(258, 184) controlPoint1: CGPointMake(240.81, 233.96) controlPoint2: CGPointMake(265.07, 221.63)]; + [path addCurveToPoint: CGPointMake(251, 147) controlPoint1: CGPointMake(255.67, 171.67) controlPoint2: CGPointMake(253.33, 159.33)]; + [path addCurveToPoint: CGPointMake(262, 79) controlPoint1: CGPointMake(253.73, 128.51) controlPoint2: CGPointMake(281.22, 99.39)]; + [path addCurveToPoint: CGPointMake(230, 113) controlPoint1: CGPointMake(260.12, 98.13) controlPoint2: CGPointMake(245.13, 107.06)]; + [path addCurveToPoint: CGPointMake(174, 146) controlPoint1: CGPointMake(229.65, 133.05) controlPoint2: CGPointMake(198.37, 155.24)]; + [path addCurveToPoint: CGPointMake(150, 119) controlPoint1: CGPointMake(166, 137) controlPoint2: CGPointMake(158, 128)]; + [path addCurveToPoint: CGPointMake(101, 96) controlPoint1: CGPointMake(138.77, 111.56) controlPoint2: CGPointMake(109.21, 105.11)]; + [path addCurveToPoint: CGPointMake(116, 69) controlPoint1: CGPointMake(103.48, 82.21) controlPoint2: CGPointMake(113.99, 82.77)]; + [path closePath]; + [path moveToPoint: CGPointMake(76, 93)]; + [path addCurveToPoint: CGPointMake(82, 93) controlPoint1: CGPointMake(78, 93) controlPoint2: CGPointMake(80, 93)]; + [path addCurveToPoint: CGPointMake(82, 98) controlPoint1: CGPointMake(82, 94.67) controlPoint2: CGPointMake(82, 96.33)]; + [path addCurveToPoint: CGPointMake(76, 98) controlPoint1: CGPointMake(80, 98) controlPoint2: CGPointMake(78, 98)]; + [path addCurveToPoint: CGPointMake(76, 93) controlPoint1: CGPointMake(76, 96.33) controlPoint2: CGPointMake(76, 94.67)]; + [path closePath]; + return path; +} + +UIBezierPath *BuildStarPath() +{ + // Create new path, courtesy of PaintCode (paintcodeapp.com) + UIBezierPath* bezierPath = [UIBezierPath bezierPath]; + + // Move to the start of the path + [bezierPath moveToPoint: CGPointMake(883.23, 430.54)]; + + // Add the cubic segments + [bezierPath addCurveToPoint: CGPointMake(749.25, 358.4) + controlPoint1: CGPointMake(873.68, 370.91) + controlPoint2: CGPointMake(809.43, 367.95)]; + [bezierPath addCurveToPoint: CGPointMake(668.1, 353.25) + controlPoint1: CGPointMake(721.92, 354.07) + controlPoint2: CGPointMake(690.4, 362.15)]; + [bezierPath addCurveToPoint: CGPointMake(492.9, 156.15) + controlPoint1: CGPointMake(575.39, 316.25) + controlPoint2: CGPointMake(629.21, 155.47)]; + [bezierPath addCurveToPoint: CGPointMake(461.98, 169.03) + controlPoint1: CGPointMake(482.59, 160.45) + controlPoint2: CGPointMake(472.29, 164.74)]; + [bezierPath addCurveToPoint: CGPointMake(365.36, 345.52) + controlPoint1: CGPointMake(409.88, 207.98) + controlPoint2: CGPointMake(415.22, 305.32)]; + [bezierPath addCurveToPoint: CGPointMake(262.31, 358.4) + controlPoint1: CGPointMake(341.9, 364.44) + controlPoint2: CGPointMake(300.41, 352.37)]; + [bezierPath addCurveToPoint: CGPointMake(133.48, 460.17) + controlPoint1: CGPointMake(200.89, 368.12) + controlPoint2: CGPointMake(118.62, 376.61)]; + [bezierPath addCurveToPoint: CGPointMake(277.77, 622.49) + controlPoint1: CGPointMake(148.46, 544.36) + controlPoint2: CGPointMake(258.55, 560.05)]; + [bezierPath addCurveToPoint: CGPointMake(277.77, 871.12) + controlPoint1: CGPointMake(301.89, 700.9) + controlPoint2: CGPointMake(193.24, 819.76)]; + [bezierPath addCurveToPoint: CGPointMake(513.51, 798.97) + controlPoint1: CGPointMake(382.76, 934.9) + controlPoint2: CGPointMake(435.24, 786.06)]; + [bezierPath addCurveToPoint: CGPointMake(723.49, 878.84) + controlPoint1: CGPointMake(582.42, 810.35) + controlPoint2: CGPointMake(628.93, 907.89)]; + [bezierPath addCurveToPoint: CGPointMake(740.24, 628.93) + controlPoint1: CGPointMake(834.7, 844.69) + controlPoint2: CGPointMake(722.44, 699.2)]; + [bezierPath addCurveToPoint: CGPointMake(883.23, 430.54) + controlPoint1: CGPointMake(756.58, 564.39) + controlPoint2: CGPointMake(899.19, 530.23)]; + + // Close the path. It’s now ready to draw + [bezierPath closePath]; + return bezierPath; +} + +UIColor *NoiseColor() +{ + static UIImage *noise = nil; + if (noise) + return [UIColor colorWithPatternImage:noise]; + + srandom(time(0)); + + CGSize size = CGSizeMake(128, 128); + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + for (int j = 0; j < size.height; j++) + for (int i = 0; i < size.height; i++) + { + UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(i, j, 1, 1)]; + CGFloat level = ((double) random() / (double) LONG_MAX); + [path fill:[UIColor colorWithWhite:level alpha:1]]; + } + noise = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return [UIColor colorWithPatternImage:noise]; +} diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index e8a194f68..f0c1707a7 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -55,6 +55,55 @@ 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; }; 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */; }; 7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */; }; + 945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */; }; + 945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */; }; + 945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */; }; + 945A8AE61F4CC037004BBF6B /* BaseGeometry.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */; }; + 945A8AE81F4CC04C004BBF6B /* BezierUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */; }; + 945A8AEA1F4CC050004BBF6B /* BezierFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */; }; + 945A8AEC1F4CC055004BBF6B /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AEB1F4CC055004BBF6B /* BezierElement.m */; }; + 945A8AEE1F4CC07A004BBF6B /* Utility.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AED1F4CC07A004BBF6B /* Utility.m */; }; + 945A8AF01F4CC092004BBF6B /* Drawing-Block.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */; }; + 945A8AF31F4CC0AF004BBF6B /* Drawing-Gradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */; }; + 945A8AF41F4CC0AF004BBF6B /* Drawing-Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */; }; + 945A8AF61F4CC0C1004BBF6B /* ImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */; }; + 945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; + 945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; + 9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */; }; + 9494C4BB1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */; }; + 9494C4BC1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */; }; + 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */; }; + 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D91F473BCB00D5BCFD /* CoreText.framework */; }; + 9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */; }; + 9494C4DE1F473BDD00D5BCFD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DD1F473BDD00D5BCFD /* UIKit.framework */; }; + 9494C4E01F473BED00D5BCFD /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DF1F473BED00D5BCFD /* Accelerate.framework */; }; + 9494C4E21F473BF500D5BCFD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4E11F473BF500D5BCFD /* Foundation.framework */; }; + 9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4E81F4B5BE700D5BCFD /* FontData.m */; }; + 9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4E81F4B5BE700D5BCFD /* FontData.m */; }; + 9494C5051F4B5BE800D5BCFD /* FontWeight.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */; }; + 9494C5061F4B5BE800D5BCFD /* FontWeight.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */; }; + 9494C5071F4B5BE800D5BCFD /* PropHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */; }; + 9494C5081F4B5BE800D5BCFD /* PropHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */; }; + 9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */; }; + 9494C5261F4B605F00D5BCFD /* GlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */; }; + 9494C5381F4C44DD00D5BCFD /* FontStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C52F1F4C44DD00D5BCFD /* FontStyle.m */; }; + 9494C5391F4C44DD00D5BCFD /* FontStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C52F1F4C44DD00D5BCFD /* FontStyle.m */; }; + 9494C53A1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */; }; + 9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */; }; + 9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */; }; + 9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */; }; + 9494C53E1F4C44DD00D5BCFD /* TextDecoration.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */; }; + 9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */; }; + 9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */; }; + 9494C5411F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */; }; + 9494C5421F4C44DD00D5BCFD /* TextPathMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */; }; + 9494C5431F4C44DD00D5BCFD /* TextPathMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */; }; + 9494C5441F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */; }; + 9494C5451F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */; }; + 9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; }; + 9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; }; + 9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; }; + 9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; }; A361E76E1EB0C33D00646005 /* RNSVGTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */; }; A361E76F1EB0C33D00646005 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; }; A361E7701EB0C33D00646005 /* RNSVGRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D471CE74E3D00887C2B /* RNSVGRect.m */; }; @@ -105,6 +154,23 @@ A361E79D1EB0C33D00646005 /* RNSVGDefsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48C1D3DDCCE0051496D /* RNSVGDefsManager.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 66F2EBE31A8DC05100D536E9; + remoteInfo = PerformanceBezier; + }; + 9494C4781F4710FE00D5BCFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 66B9D28C1A8D5FDE00CAC341; + remoteInfo = PerformanceBezierTests; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -231,6 +297,56 @@ 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = ""; }; 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSVGGlyphContext.h; path = Text/RNSVGGlyphContext.h; sourceTree = ""; }; 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGlyphContext.m; path = Text/RNSVGGlyphContext.m; sourceTree = ""; }; + 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */ = {isa = PBXFileReference; lastKnownFileType = folder; path = QuartzBookPack; sourceTree = ""; }; + 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Text.m"; path = "QuartzBookPack/TextDrawing/Drawing-Text.m"; sourceTree = ""; }; + 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Text.m"; path = "QuartzBookPack/TextDrawing/UIBezierPath+Text.m"; sourceTree = ""; }; + 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Elements.m"; path = "QuartzBookPack/Bezier/UIBezierPath+Elements.m"; sourceTree = ""; }; + 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BaseGeometry.m; path = QuartzBookPack/Geometry/BaseGeometry.m; sourceTree = ""; }; + 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierUtils.m; path = QuartzBookPack/Bezier/BezierUtils.m; sourceTree = ""; }; + 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierFunctions.m; path = QuartzBookPack/Bezier/BezierFunctions.m; sourceTree = ""; }; + 945A8AEB1F4CC055004BBF6B /* BezierElement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierElement.m; path = QuartzBookPack/Bezier/BezierElement.m; sourceTree = ""; }; + 945A8AED1F4CC07A004BBF6B /* Utility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Utility.m; path = QuartzBookPack/Utility/Utility.m; sourceTree = ""; }; + 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Block.m"; path = "QuartzBookPack/Drawing/Drawing-Block.m"; sourceTree = ""; }; + 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Gradient.m"; path = "QuartzBookPack/Drawing/Drawing-Gradient.m"; sourceTree = ""; }; + 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Util.m"; path = "QuartzBookPack/Drawing/Drawing-Util.m"; sourceTree = ""; }; + 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ImageUtils.m; path = QuartzBookPack/Image/ImageUtils.m; sourceTree = ""; }; + 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlignmentBaseline.m; path = Text/AlignmentBaseline.m; sourceTree = ""; }; + 945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AlignmentBaseline.h; path = Text/AlignmentBaseline.h; sourceTree = ""; }; + 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PerformanceBezier.xcodeproj; path = PerformanceBezier/PerformanceBezier.xcodeproj; sourceTree = ""; }; + 9494C4B91F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath+getTransformAtDistance.h"; path = "Utils/UIBezierPath+getTransformAtDistance.h"; sourceTree = ""; }; + 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+getTransformAtDistance.m"; path = "Utils/UIBezierPath+getTransformAtDistance.m"; sourceTree = ""; }; + 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 9494C4D91F473BCB00D5BCFD /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; + 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 9494C4DD1F473BDD00D5BCFD /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 9494C4DF1F473BED00D5BCFD /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + 9494C4E11F473BF500D5BCFD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 9494C4E71F4B5BE700D5BCFD /* FontData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontData.h; path = Text/FontData.h; sourceTree = ""; }; + 9494C4E81F4B5BE700D5BCFD /* FontData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontData.m; path = Text/FontData.m; sourceTree = ""; }; + 9494C4E91F4B5BE700D5BCFD /* FontStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontStyle.h; path = Text/FontStyle.h; sourceTree = ""; }; + 9494C4EB1F4B5BE700D5BCFD /* FontVariantLigatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontVariantLigatures.h; path = Text/FontVariantLigatures.h; sourceTree = ""; }; + 9494C4ED1F4B5BE700D5BCFD /* FontWeight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontWeight.h; path = Text/FontWeight.h; sourceTree = ""; }; + 9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontWeight.m; path = Text/FontWeight.m; sourceTree = ""; }; + 9494C4EF1F4B5BE700D5BCFD /* PropHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PropHelper.h; path = Text/PropHelper.h; sourceTree = ""; }; + 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PropHelper.m; path = Text/PropHelper.m; sourceTree = ""; }; + 9494C4F11F4B5BE700D5BCFD /* TextAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextAnchor.h; path = Text/TextAnchor.h; sourceTree = ""; }; + 9494C4F31F4B5BE700D5BCFD /* TextDecoration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextDecoration.h; path = Text/TextDecoration.h; sourceTree = ""; }; + 9494C4F51F4B5BE700D5BCFD /* TextLengthAdjust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextLengthAdjust.h; path = Text/TextLengthAdjust.h; sourceTree = ""; }; + 9494C4F71F4B5BE700D5BCFD /* TextPathMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathMethod.h; path = Text/TextPathMethod.h; sourceTree = ""; }; + 9494C4F91F4B5BE700D5BCFD /* TextPathMidLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathMidLine.h; path = Text/TextPathMidLine.h; sourceTree = ""; }; + 9494C4FB1F4B5BE700D5BCFD /* TextPathSide.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathSide.h; path = Text/TextPathSide.h; sourceTree = ""; }; + 9494C4FD1F4B5BE700D5BCFD /* TextPathSpacing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathSpacing.h; path = Text/TextPathSpacing.h; sourceTree = ""; }; + 9494C5191F4B605F00D5BCFD /* GlyphContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GlyphContext.h; path = Text/GlyphContext.h; sourceTree = ""; }; + 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GlyphContext.m; path = Text/GlyphContext.m; sourceTree = ""; }; + 9494C52F1F4C44DD00D5BCFD /* FontStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontStyle.m; path = Text/FontStyle.m; sourceTree = ""; }; + 9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontVariantLigatures.m; path = Text/FontVariantLigatures.m; sourceTree = ""; }; + 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextAnchor.m; path = Text/TextAnchor.m; sourceTree = ""; }; + 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextDecoration.m; path = Text/TextDecoration.m; sourceTree = ""; }; + 9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextLengthAdjust.m; path = Text/TextLengthAdjust.m; sourceTree = ""; }; + 9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathMethod.m; path = Text/TextPathMethod.m; sourceTree = ""; }; + 9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathMidLine.m; path = Text/TextPathMidLine.m; sourceTree = ""; }; + 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSide.m; path = Text/TextPathSide.m; sourceTree = ""; }; + 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSpacing.m; path = Text/TextPathSpacing.m; sourceTree = ""; }; 94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -239,6 +355,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9494C4E01F473BED00D5BCFD /* Accelerate.framework in Frameworks */, + 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */, + 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */, + 9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */, + 9494C4DE1F473BDD00D5BCFD /* UIKit.framework in Frameworks */, + 9494C4E21F473BF500D5BCFD /* Foundation.framework in Frameworks */, + 9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -255,6 +378,20 @@ 0CF68AB81AF0540F00FF9E5C = { isa = PBXGroup; children = ( + 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */, + 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */, + 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */, + 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */, + 945A8AED1F4CC07A004BBF6B /* Utility.m */, + 945A8AEB1F4CC055004BBF6B /* BezierElement.m */, + 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */, + 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */, + 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */, + 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */, + 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */, + 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */, + 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */, + 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */, 1039D29A1CE7212C001E90A8 /* Utils */, 1039D2801CE71DCF001E90A8 /* Elements */, 1039D27F1CE71D9B001E90A8 /* Text */, @@ -267,6 +404,7 @@ 0CF68AE11AF0549300FF9E5C /* RNSVGRenderable.h */, 0CF68AE21AF0549300FF9E5C /* RNSVGRenderable.m */, 0CF68AC21AF0540F00FF9E5C /* Products */, + 9494C2B31F46139600D5BCFD /* Frameworks */, ); sourceTree = ""; }; @@ -358,16 +496,44 @@ 1039D27F1CE71D9B001E90A8 /* Text */ = { isa = PBXGroup; children = ( + 945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */, + 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */, + 9494C4E71F4B5BE700D5BCFD /* FontData.h */, + 9494C4E81F4B5BE700D5BCFD /* FontData.m */, + 9494C4E91F4B5BE700D5BCFD /* FontStyle.h */, + 9494C52F1F4C44DD00D5BCFD /* FontStyle.m */, + 9494C4EB1F4B5BE700D5BCFD /* FontVariantLigatures.h */, + 9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */, + 9494C4ED1F4B5BE700D5BCFD /* FontWeight.h */, + 9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */, + 9494C5191F4B605F00D5BCFD /* GlyphContext.h */, + 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */, + 9494C4EF1F4B5BE700D5BCFD /* PropHelper.h */, + 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */, + 103371331D41D3400028AF13 /* RNSVGBezierTransformer.h */, + 103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */, 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */, 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */, + 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, + 1039D2901CE71EC2001E90A8 /* RNSVGText.m */, 7F08CE9C1E23479700650F83 /* RNSVGTextPath.h */, 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */, 7F08CE9E1E23479700650F83 /* RNSVGTSpan.h */, 7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */, - 103371331D41D3400028AF13 /* RNSVGBezierTransformer.h */, - 103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */, - 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, - 1039D2901CE71EC2001E90A8 /* RNSVGText.m */, + 9494C4F11F4B5BE700D5BCFD /* TextAnchor.h */, + 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */, + 9494C4F31F4B5BE700D5BCFD /* TextDecoration.h */, + 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */, + 9494C4F51F4B5BE700D5BCFD /* TextLengthAdjust.h */, + 9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */, + 9494C4F71F4B5BE700D5BCFD /* TextPathMethod.h */, + 9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */, + 9494C4F91F4B5BE700D5BCFD /* TextPathMidLine.h */, + 9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */, + 9494C4FB1F4B5BE700D5BCFD /* TextPathSide.h */, + 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */, + 9494C4FD1F4B5BE700D5BCFD /* TextPathSpacing.h */, + 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */, ); name = Text; sourceTree = ""; @@ -415,10 +581,34 @@ 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, + 9494C4B91F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.h */, + 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */, ); name = Utils; sourceTree = ""; }; + 9494C2B31F46139600D5BCFD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9494C4E11F473BF500D5BCFD /* Foundation.framework */, + 9494C4DF1F473BED00D5BCFD /* Accelerate.framework */, + 9494C4DD1F473BDD00D5BCFD /* UIKit.framework */, + 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */, + 9494C4D91F473BCB00D5BCFD /* CoreText.framework */, + 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9494C4721F4710FE00D5BCFD /* Products */ = { + isa = PBXGroup; + children = ( + 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */, + 9494C4791F4710FE00D5BCFD /* PerformanceBezierTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -479,6 +669,12 @@ mainGroup = 0CF68AB81AF0540F00FF9E5C; productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 9494C4721F4710FE00D5BCFD /* Products */; + ProjectRef = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; + }, + ); projectRoot = ""; targets = ( 0CF68AC01AF0540F00FF9E5C /* RNSVG */, @@ -487,21 +683,54 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework.static; + path = PerformanceBezier.framework; + remoteRef = 9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 9494C4791F4710FE00D5BCFD /* PerformanceBezierTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PerformanceBezierTests.xctest; + remoteRef = 9494C4781F4710FE00D5BCFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXSourcesBuildPhase section */ 0CF68ABD1AF0540F00FF9E5C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 945A8AF01F4CC092004BBF6B /* Drawing-Block.m in Sources */, + 945A8AF31F4CC0AF004BBF6B /* Drawing-Gradient.m in Sources */, + 945A8AF41F4CC0AF004BBF6B /* Drawing-Util.m in Sources */, + 945A8AF61F4CC0C1004BBF6B /* ImageUtils.m in Sources */, + 945A8AEE1F4CC07A004BBF6B /* Utility.m in Sources */, + 945A8AE81F4CC04C004BBF6B /* BezierUtils.m in Sources */, + 945A8AEC1F4CC055004BBF6B /* BezierElement.m in Sources */, + 945A8AEA1F4CC050004BBF6B /* BezierFunctions.m in Sources */, + 945A8AE61F4CC037004BBF6B /* BaseGeometry.m in Sources */, + 945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */, + 945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */, + 945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */, 10BA0D3F1CE74E3100887C2B /* RNSVGTextManager.m in Sources */, 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */, 10BA0D4B1CE74E3D00887C2B /* RNSVGRect.m in Sources */, 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */, 10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */, + 9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */, 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */, 7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */, + 9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */, 10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */, + 9494C5051F4B5BE800D5BCFD /* FontWeight.m in Sources */, 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */, 7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */, + 9494C4BB1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */, 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */, 10BEC1C21D3F680F00FDCB19 /* RNSVGLinearGradientManager.m in Sources */, 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */, @@ -509,7 +738,10 @@ 0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */, 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */, 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */, + 945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */, 10BEC1C61D3F7BD300FDCB19 /* RNSVGPainter.m in Sources */, + 9494C5381F4C44DD00D5BCFD /* FontStyle.m in Sources */, + 9494C5071F4B5BE800D5BCFD /* PropHelper.m in Sources */, 10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */, 10ED4A9B1CF065260078BC02 /* RNSVGClipPath.m in Sources */, 10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */, @@ -519,14 +751,18 @@ 10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */, 10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */, 10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */, + 9494C5441F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */, 10BA0D371CE74E3100887C2B /* RNSVGImageManager.m in Sources */, 10BA0D391CE74E3100887C2B /* RNSVGNodeManager.m in Sources */, 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */, 1023B4901D3DF4C40051496D /* RNSVGDefs.m in Sources */, 10BA0D381CE74E3100887C2B /* RNSVGLineManager.m in Sources */, + 9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */, 10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */, + 9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */, 10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */, 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */, + 9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */, 0CF68B0B1AF0549300FF9E5C /* RNSVGBrush.m in Sources */, 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */, 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */, @@ -534,12 +770,16 @@ 7F08CE9A1E23476900650F83 /* RNSVGTextPathManager.m in Sources */, 7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */, 7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */, + 9494C53E1F4C44DD00D5BCFD /* TextDecoration.m in Sources */, 7F08CEA11E23479700650F83 /* RNSVGTSpan.m in Sources */, + 9494C5421F4C44DD00D5BCFD /* TextPathMethod.m in Sources */, 10BA0D4A1CE74E3D00887C2B /* RNSVGLine.m in Sources */, 10FDEEB21D3FB60500A5C46C /* RNSVGPainterBrush.m in Sources */, 1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */, 1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */, 1023B48D1D3DDCCE0051496D /* RNSVGDefsManager.m in Sources */, + 9494C53A1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */, + 9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -554,11 +794,14 @@ A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */, A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */, A361E7741EB0C33D00646005 /* RNSVGGlyphContext.m in Sources */, + 9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */, A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */, A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */, A361E7771EB0C33D00646005 /* RNSVGTextPath.m in Sources */, + 9494C4BC1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */, A361E7781EB0C33D00646005 /* RNSVGUse.m in Sources */, A361E7791EB0C33D00646005 /* RNSVGLinearGradientManager.m in Sources */, + 9494C5061F4B5BE800D5BCFD /* FontWeight.m in Sources */, A361E77A1EB0C33D00646005 /* RNSVGText.m in Sources */, A361E77B1EB0C33D00646005 /* RNSVGRectManager.m in Sources */, A361E77C1EB0C33D00646005 /* RNSVGRenderable.m in Sources */, @@ -567,19 +810,31 @@ A361E77F1EB0C33D00646005 /* RNSVGPainter.m in Sources */, A361E7801EB0C33D00646005 /* RNSVGNode.m in Sources */, A361E7811EB0C33D00646005 /* RNSVGClipPath.m in Sources */, + 9494C5081F4B5BE800D5BCFD /* PropHelper.m in Sources */, A361E7821EB0C33D00646005 /* RNSVGSvgViewManager.m in Sources */, + 9494C5411F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */, + 9494C5261F4B605F00D5BCFD /* GlyphContext.m in Sources */, A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */, + 9494C5391F4C44DD00D5BCFD /* FontStyle.m in Sources */, A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */, A361E7851EB0C33D00646005 /* RNSVGBezierTransformer.m in Sources */, A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */, A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */, + 945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */, A361E7881EB0C33D00646005 /* RNSVGRadialGradientManager.m in Sources */, A361E7891EB0C33D00646005 /* RNSVGImageManager.m in Sources */, A361E78A1EB0C33D00646005 /* RNSVGNodeManager.m in Sources */, A361E78B1EB0C33D00646005 /* RNSVGSymbol.m in Sources */, + 9494C5431F4C44DD00D5BCFD /* TextPathMethod.m in Sources */, A361E78C1EB0C33D00646005 /* RNSVGDefs.m in Sources */, + 9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */, + 9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */, + 9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */, A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */, + 9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */, + 9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */, A361E78E1EB0C33D00646005 /* RNSVGCircle.m in Sources */, + 9494C5451F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */, A361E78F1EB0C33D00646005 /* RNSVGEllipseManager.m in Sources */, A361E7901EB0C33D00646005 /* RCTConvert+RNSVG.m in Sources */, A361E7911EB0C33D00646005 /* RNSVGBrush.m in Sources */, @@ -642,6 +897,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 7.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-ObjC++", + "-lstdc++", + ); SDKROOT = iphoneos; }; name = Debug; @@ -680,6 +939,10 @@ ); IPHONEOS_DEPLOYMENT_TARGET = 7.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "-ObjC++", + "-lstdc++", + ); SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; @@ -688,12 +951,23 @@ 0CF68AD61AF0540F00FF9E5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULE_DEBUGGING = YES; + FRAMEWORK_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(BUILT_PRODUCTS_DIR)/usr/local/include", + ./PerformanceBezier, + "./QuartzBookPack/**", ); - OTHER_LDFLAGS = "-ObjC"; + LIBRARY_SEARCH_PATHS = ""; + OTHER_LDFLAGS = ( + "-ObjC++", + "-lstdc++", + "-all_load", + "-ObjC", + ); + PRELINK_LIBS = ""; PRODUCT_NAME = RNSVG; PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; SKIP_INSTALL = YES; @@ -703,12 +977,23 @@ 0CF68AD71AF0540F00FF9E5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULE_DEBUGGING = YES; + FRAMEWORK_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(BUILT_PRODUCTS_DIR)/usr/local/include", + ./PerformanceBezier, + "./QuartzBookPack/**", ); - OTHER_LDFLAGS = "-ObjC"; + LIBRARY_SEARCH_PATHS = ""; + OTHER_LDFLAGS = ( + "-ObjC++", + "-lstdc++", + "-all_load", + "-ObjC", + ); + PRELINK_LIBS = ""; PRODUCT_NAME = RNSVG; PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; SKIP_INSTALL = YES; diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index bf2f8dd3d..6c11764b9 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -11,11 +11,13 @@ #import "RNSVGClipPath.h" #import "RNSVGGroup.h" #import "RNSVGGlyphContext.h" +#import "GlyphContext.h" @implementation RNSVGNode { RNSVGGroup *_textRoot; - RNSVGGlyphContext *glyphContext; + GlyphContext *glyphContext; + RNSVGGlyphContext *RNSVGGlyphContext; BOOL _transparent; CGPathRef _cachedClipPath; RNSVGSvgView *_svgView; @@ -240,12 +242,20 @@ - (RNSVGSvgView *)getSvgView - (CGFloat)relativeOnWidth:(NSString *)length { - return [RNSVGPercentageConverter stringToFloat:length relative:[self getContextWidth] offset:0]; + return [PropHelper fromRelativeWithNSString:length + relative:[self getContextWidth] + offset:0 + scale:1 + fontSize:[self getFontSizeFromContext]]; } - (CGFloat)relativeOnHeight:(NSString *)length { - return [RNSVGPercentageConverter stringToFloat:length relative:[self getContextHeight] offset:0]; + return [PropHelper fromRelativeWithNSString:length + relative:[self getContextHeight] + offset:0 + scale:1 + fontSize:[self getFontSizeFromContext]]; } - (CGFloat)relativeOnOther:(NSString *)length @@ -255,25 +265,32 @@ - (CGFloat)relativeOnOther:(NSString *)length CGFloat powX = width * width; CGFloat powY = height * height; CGFloat r = sqrt(powX + powY) * M_SQRT1_2l; - return [RNSVGPercentageConverter stringToFloat:length relative:r offset:0];} + return [PropHelper fromRelativeWithNSString:length + relative:r + offset:0 + scale:1 + fontSize:[self getFontSizeFromContext]]; +} - (CGFloat)getContextWidth { RNSVGGroup * root = [self getTextRoot]; - if (root == nil) { + GlyphContext * gc = [root getGlyphContext]; + if (root == nil || gc == nil) { return CGRectGetWidth([[self getSvgView] getContextBounds]); } else { - return [[root getGlyphContext] getWidth]; + return [gc getWidth]; } } - (CGFloat)getContextHeight { RNSVGGroup * root = [self getTextRoot]; - if (root == nil) { + GlyphContext * gc = [root getGlyphContext]; + if (root == nil || gc == nil) { return CGRectGetHeight([[self getSvgView] getContextBounds]); } else { - return [[root getGlyphContext] getHeight]; + return [gc getHeight]; } } diff --git a/ios/Text/AlignmentBaseline.h b/ios/Text/AlignmentBaseline.h new file mode 100644 index 000000000..f0858b0ab --- /dev/null +++ b/ios/Text/AlignmentBaseline.h @@ -0,0 +1,63 @@ +#ifndef AlignmentBaseline_h +#define AlignmentBaseline_h + +#import + +NS_ENUM(NSInteger, AlignmentBaseline) { + AlignmentBaselineBaseline, + AlignmentBaselineTextBottom, + AlignmentBaselineAlphabetic, + AlignmentBaselineIdeographic, + AlignmentBaselineMiddle, + AlignmentBaselineCentral, + AlignmentBaselineMathematical, + AlignmentBaselineTextTop, + AlignmentBaselineBottom, + AlignmentBaselineCenter, + AlignmentBaselineTop, + /* + SVG implementations may support the following aliases in order to support legacy content: + + text-before-edge = text-top + text-after-edge = text-bottom + */ + AlignmentBaselineTextBeforeEdge, + AlignmentBaselineTextAfterEdge, + // SVG 1.1 + AlignmentBaselineBeforeEdge, + AlignmentBaselineAfterEdge, + AlignmentBaselineHanging, + AlignmentBaselineDEFAULT = AlignmentBaselineBaseline +}; + +static NSString* const AlignmentBaselineStrings[] = { + @"baseline", + @"text-bottom", + @"alphabetic", + @"ideographic", + @"middle", + @"central", + @"mathematical", + @"text-top", + @"bottom", + @"center", + @"top", + @"text-before-edge", + @"text-after-edge", + @"before-edge", + @"after-edge", + @"hanging", + @"central", + @"mathematical", + @"text-top", + @"bottom", + @"center", + @"top", + nil +}; + +NSString* AlignmentBaselineToString( enum AlignmentBaseline fw ); + +enum AlignmentBaseline AlignmentBaselineFromString( NSString* s ); + +#endif /* AlignmentBaseline_h */ diff --git a/ios/Text/AlignmentBaseline.m b/ios/Text/AlignmentBaseline.m new file mode 100644 index 000000000..b28d6f251 --- /dev/null +++ b/ios/Text/AlignmentBaseline.m @@ -0,0 +1,18 @@ +#import "AlignmentBaseline.h" + +NSString* AlignmentBaselineToString( enum AlignmentBaseline fw ) +{ + return AlignmentBaselineStrings[fw]; +} + +enum AlignmentBaseline AlignmentBaselineFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = AlignmentBaselineStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return AlignmentBaselineDEFAULT; +} diff --git a/ios/Text/FontData.h b/ios/Text/FontData.h new file mode 100644 index 000000000..ec8acc9b7 --- /dev/null +++ b/ios/Text/FontData.h @@ -0,0 +1,43 @@ +#import +#import + +#import "FontStyle.h" +#import "FontVariantLigatures.h" +#import "FontWeight.h" +#import "PropHelper.h" +#import "TextAnchor.h" +#import "TextDecoration.h" + +@interface FontData : NSObject { +@public + double fontSize; + NSString * fontSize_; + NSString *fontFamily; + enum FontStyle fontStyle; + NSString * fontStyle_; + NSDictionary * fontData; + enum FontWeight fontWeight; + NSString * fontWeight_; + NSString *fontFeatureSettings; + enum FontVariantLigatures fontVariantLigatures; + enum TextAnchor textAnchor; + enum TextDecoration textDecoration; + double kerning; + double wordSpacing; + double letterSpacing; + bool manualKerning; +} + ++ (instancetype)Defaults; + ++ (double)toAbsoluteWithNSString:(NSString *)string + scale:(double)scale + fontSize:(double)fontSize; + ++ (instancetype)initWithNSDictionary:(NSDictionary *)font + parent:(FontData *)parent + scale:(double)scale; + +@end + +#define FontData_DEFAULT_FONT_SIZE 12.0 diff --git a/ios/Text/FontData.m b/ios/Text/FontData.m new file mode 100644 index 000000000..e1c2b2666 --- /dev/null +++ b/ios/Text/FontData.m @@ -0,0 +1,108 @@ +#import "FontData.h" +#import "FontStyle.h" +#import "FontVariantLigatures.h" +#import "FontWeight.h" +#import "PropHelper.h" +#import "TextAnchor.h" +#import "TextDecoration.h" +#import "RNSVGNode.h" + +#define DEFAULT_KERNING 0.0 +#define DEFAULT_WORD_SPACING 0.0 +#define DEFAULT_LETTER_SPACING 0.0 +static NSString *KERNING = @"kerning"; +static NSString *FONT_SIZE = @"fontSize"; +static NSString *FONT_DATA = @"fontData"; +static NSString *FONT_STYLE = @"fontStyle"; +static NSString *FONT_WEIGHT = @"fontWeight"; +static NSString *FONT_FAMILY = @"fontFamily"; +static NSString *TEXT_ANCHOR = @"textAnchor"; +static NSString *WORD_SPACING = @"wordSpacing"; +static NSString *LETTER_SPACING = @"letterSpacing"; +static NSString *TEXT_DECORATION = @"textDecoration"; +static NSString *FONT_FEATURE_SETTINGS = @"fontFeatureSettings"; +static NSString *FONT_VARIANT_LIGATURES = @"fontVariantLigatures"; + +FontData *FontData_Defaults; + +@implementation FontData + ++ (instancetype)Defaults { + if (!FontData_Defaults) { + FontData *self = [FontData alloc]; + self->fontData = nil; + self->fontFamily = @""; + self->fontStyle = FontStyleNormal; + self->fontWeight = FontWeightNormal; + self->fontFeatureSettings = @""; + self->fontVariantLigatures = FontVariantLigaturesNormal; + self->textAnchor = TextAnchorStart; + self->textDecoration = TextDecorationNone; + self->manualKerning = false; + self->kerning = DEFAULT_KERNING; + self->fontSize = DEFAULT_FONT_SIZE; + self->wordSpacing = DEFAULT_WORD_SPACING; + self->letterSpacing = DEFAULT_LETTER_SPACING; + FontData_Defaults = self; + } + return FontData_Defaults; +} + ++ (double)toAbsoluteWithNSString:(NSString *)string + scale:(double)scale + fontSize:(double)fontSize { + return [PropHelper fromRelativeWithNSString:string + relative:0 + offset:0 + scale:scale + fontSize:fontSize]; +} + ++ (instancetype)initWithNSDictionary:(NSDictionary *)font + parent:(FontData *)parent + scale:(double)scale { + FontData *data = [FontData alloc]; + double parentFontSize = parent->fontSize; + if ([font objectForKey:FONT_SIZE]) { + NSString *string = [font objectForKey:FONT_SIZE]; + data->fontSize = [PropHelper fromRelativeWithNSString:string + relative:parentFontSize + offset:0 + scale:scale + fontSize:parentFontSize]; + } + else { + data->fontSize = parentFontSize; + } + data->fontData = [font objectForKey:FONT_DATA] ? [font objectForKey:FONT_DATA] : parent->fontData; + data->fontFamily = [font objectForKey:FONT_FAMILY] ? [font objectForKey:FONT_FAMILY] : parent->fontFamily; + NSString* style = [font objectForKey:FONT_STYLE]; + data->fontStyle = style ? FontStyleFromString(style) : parent->fontStyle; + NSString* weight = [font objectForKey:FONT_WEIGHT]; + data->fontWeight = weight ? FontWeightFromString(weight) : parent->fontWeight; + NSString* feature = [font objectForKey:FONT_FEATURE_SETTINGS]; + data->fontFeatureSettings = feature ? [font objectForKey:FONT_FEATURE_SETTINGS] : parent->fontFeatureSettings; + NSString* variant = [font objectForKey:FONT_VARIANT_LIGATURES]; + data->fontVariantLigatures = variant ? FontVariantLigaturesFromString(variant) : parent->fontVariantLigatures; + NSString* anchor = [font objectForKey:TEXT_ANCHOR]; + data->textAnchor = anchor ? TextAnchorFromString(anchor) : parent->textAnchor; + NSString* decoration = [font objectForKey:TEXT_DECORATION]; + data->textDecoration = decoration ? TextDecorationFromString(decoration) : parent->textDecoration; + NSString* kerning = [font objectForKey:KERNING]; + data->manualKerning = (kerning || parent->manualKerning ); + data->kerning = kerning ? [FontData toAbsoluteWithNSString:kerning + scale:scale + fontSize:data->fontSize ] : parent->kerning; + NSString* wordSpacing = [font objectForKey:WORD_SPACING]; + data->wordSpacing = wordSpacing ? [FontData toAbsoluteWithNSString:wordSpacing + scale:scale + fontSize:data->fontSize ] : parent->wordSpacing; + NSString* letterSpacing = [font objectForKey:LETTER_SPACING]; + data->letterSpacing = letterSpacing ? [FontData toAbsoluteWithNSString:letterSpacing + scale:scale + fontSize:data->fontSize ] : parent->letterSpacing; + return data; +} + + +@end diff --git a/ios/Text/FontStyle.h b/ios/Text/FontStyle.h new file mode 100644 index 000000000..c3db08642 --- /dev/null +++ b/ios/Text/FontStyle.h @@ -0,0 +1,19 @@ +#import + +#if !defined (FontStyle_) +#define FontStyle_ + +NS_ENUM(NSInteger, FontStyle) { + FontStyleNormal, + FontStyleItalic, + FontStyleOblique, + FontStyleDEFAULT = FontStyleNormal, +}; + +static NSString* const FontStyleStrings[] = {@"normal", @"italic", @"oblique", nil}; + +NSString* FontStyleToString( enum FontStyle fw ); + +enum FontStyle FontStyleFromString( NSString* s ); + +#endif diff --git a/ios/Text/FontStyle.m b/ios/Text/FontStyle.m new file mode 100644 index 000000000..8c61d9e0e --- /dev/null +++ b/ios/Text/FontStyle.m @@ -0,0 +1,18 @@ +#import "FontStyle.h" + +NSString* FontStyleToString( enum FontStyle fw ) +{ + return FontStyleStrings[fw]; +} + +enum FontStyle FontStyleFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = FontStyleStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return FontStyleDEFAULT; +} diff --git a/ios/Text/FontVariantLigatures.h b/ios/Text/FontVariantLigatures.h new file mode 100644 index 000000000..29494e383 --- /dev/null +++ b/ios/Text/FontVariantLigatures.h @@ -0,0 +1,18 @@ +#import + +#if !defined (FontVariantLigatures_) +#define FontVariantLigatures_ + +NS_ENUM(NSInteger, FontVariantLigatures) { + FontVariantLigaturesNormal, + FontVariantLigaturesNone, + FontVariantLigaturesDEFAULT = FontVariantLigaturesNormal, +}; + +static NSString* const FontVariantLigaturesStrings[] = {@"normal", @"none", nil}; + +NSString* FontVariantLigaturesToString( enum FontVariantLigatures fw ); + +enum FontVariantLigatures FontVariantLigaturesFromString( NSString* s ); + +#endif diff --git a/ios/Text/FontVariantLigatures.m b/ios/Text/FontVariantLigatures.m new file mode 100644 index 000000000..3d182af5a --- /dev/null +++ b/ios/Text/FontVariantLigatures.m @@ -0,0 +1,18 @@ +#import "FontVariantLigatures.h" + +NSString* FontVariantLigaturesToString( enum FontVariantLigatures fw ) +{ + return FontVariantLigaturesStrings[fw]; +} + +enum FontVariantLigatures FontVariantLigaturesFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = FontVariantLigaturesStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return FontVariantLigaturesDEFAULT; +} diff --git a/ios/Text/FontWeight.h b/ios/Text/FontWeight.h new file mode 100644 index 000000000..9118343a8 --- /dev/null +++ b/ios/Text/FontWeight.h @@ -0,0 +1,30 @@ +#import + +#if !defined (FontWeight_) +#define FontWeight_ + +NS_ENUM(NSInteger, FontWeight) { + FontWeightNormal, + FontWeightBold, + FontWeightBolder, + FontWeightLighter, + FontWeight100, + FontWeight200, + FontWeight300, + FontWeight400, + FontWeight500, + FontWeight600, + FontWeight700, + FontWeight800, + FontWeight900, + FontWeightDEFAULT = FontWeightNormal, +}; + +static NSString* const FontWeightStrings[] = {@"Normal", @"Bold", @"Bolder", @"Lighter", @"100", @"200", @"300", @"400", @"500", @"600", @"700", @"800", @"900", nil}; + + +NSString* FontWeightToString( enum FontWeight fw ); + +enum FontWeight FontWeightFromString( NSString* s ); + +#endif diff --git a/ios/Text/FontWeight.m b/ios/Text/FontWeight.m new file mode 100644 index 000000000..230f1dc25 --- /dev/null +++ b/ios/Text/FontWeight.m @@ -0,0 +1,18 @@ +#import "FontWeight.h" + +NSString* FontWeightToString( enum FontWeight fw ) +{ + return FontWeightStrings[fw]; +} + +enum FontWeight FontWeightFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = FontWeightStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return FontWeightDEFAULT; +} diff --git a/ios/Text/GlyphContext.h b/ios/Text/GlyphContext.h new file mode 100644 index 000000000..1c2099d3d --- /dev/null +++ b/ios/Text/GlyphContext.h @@ -0,0 +1,51 @@ + +#import +#import +#import "FontData.h" + +@class RNSVGText; +@class RNSVGGroup; +@class GlyphContext; + +@interface GlyphContext : NSObject + +- (CTFontRef)getGlyphFont; + +- (instancetype)initWithScale:(float)scale_ + width:(float)width + height:(float)height; + +- (FontData *)getFont; + +- (double)getFontSize; + +- (float)getHeight; + +- (float)getWidth; + +- (double)nextDeltaX; + +- (double)nextDeltaY; + +- (NSNumber*)nextRotation; + +- (double)nextXWithDouble:(double)advance; + +- (double)nextY; + +- (void)popContext; + +- (void)pushContextwithRNSVGText:(RNSVGText *)node + reset:(bool)reset + font:(NSDictionary *)font + x:(NSArray*)x + y:(NSArray*)y + deltaX:(NSArray*)deltaX + deltaY:(NSArray*)deltaY + rotate:(NSArray*)rotate; + +- (void)pushContextWithRNSVGGroup:(RNSVGGroup*)node + font:(NSDictionary *)font; + + +@end diff --git a/ios/Text/GlyphContext.m b/ios/Text/GlyphContext.m new file mode 100644 index 000000000..510bda630 --- /dev/null +++ b/ios/Text/GlyphContext.m @@ -0,0 +1,437 @@ +// +// Generated by the J2ObjC translator. DO NOT EDIT! +// source: GlyphContext.java +// + +#import "GlyphContext.h" +#import +#import "RNSVGNode.h" +#import "PropHelper.h" +#import "FontData.h" +#import "RNSVGText.h" + +@interface GlyphContext () { +@public + NSMutableArray *mFontContext_; + NSMutableArray *mXsContext_; + NSMutableArray *mYsContext_; + NSMutableArray *mDXsContext_; + NSMutableArray *mDYsContext_; + NSMutableArray *mRsContext_; + NSMutableArray *mXIndices_; + NSMutableArray *mYIndices_; + NSMutableArray *mDXIndices_; + NSMutableArray *mDYIndices_; + NSMutableArray *mRIndices_; + NSMutableArray *mXsIndices_; + NSMutableArray *mYsIndices_; + NSMutableArray *mDXsIndices_; + NSMutableArray *mDYsIndices_; + NSMutableArray *mRsIndices_; + double mFontSize_; + FontData *topFont_; + double mX_; + double mY_; + double mDX_; + double mDY_; + NSArray *mXs_; + NSArray *mYs_; + NSArray *mDXs_; + NSArray *mDYs_; + NSArray *mRs_; + int mXsIndex_; + int mYsIndex_; + int mDXsIndex_; + int mDYsIndex_; + int mRsIndex_; + int mXIndex_; + int mYIndex_; + int mDXIndex_; + int mDYIndex_; + int mRIndex_; + int mTop_; + float mScale_; + float mWidth_; + float mHeight_; +} + +- (void)pushIndices; + +- (void)reset; + +- (FontData *)getTopOrParentFontWithRNSVGGroup:(RNSVGGroup *)child; + +- (void)pushNodeAndFontWithRNSVGGroup:(RNSVGGroup *)node + withNSDictionary:(NSDictionary *)font; + ++ (void)incrementIndicesWithNSArray:(NSArray *)indices + withInt:(int)topIndex; + +- (void)pushContextwithRNSVGText:(RNSVGText *)node + reset:(bool)reset + font:(NSDictionary *)font + x:(NSArray*)x + y:(NSArray*)y + deltaX:(NSArray*)deltaX + deltaY:(NSArray*)deltaY + rotate:(NSArray*)rotate; + +- (void)pushContextWithRNSVGGroup:(RNSVGGroup*)node + font:(NSDictionary *)font; +@end + +@implementation GlyphContext + + +- (CTFontRef)getGlyphFont +{ + NSString *fontFamily = topFont_->fontFamily; + NSNumber * fontSize = [NSNumber numberWithDouble:topFont_->fontSize]; + NSString * fontWeight = topFont_->fontWeight_; + NSString * fontStyle = topFont_->fontStyle_; + + BOOL fontFamilyFound = NO; + NSArray *supportedFontFamilyNames = [UIFont familyNames]; + + if ([supportedFontFamilyNames containsObject:fontFamily]) { + fontFamilyFound = YES; + } else { + for (NSString *fontFamilyName in supportedFontFamilyNames) { + if ([[UIFont fontNamesForFamilyName: fontFamilyName] containsObject:fontFamily]) { + fontFamilyFound = YES; + break; + } + } + } + fontFamily = fontFamilyFound ? fontFamily : nil; + + return (__bridge CTFontRef)[RCTFont updateFont:nil + withFamily:fontFamily + size:fontSize + weight:fontWeight + style:fontStyle + variant:nil + scaleMultiplier:1.0]; +} + +- (void)pushIndices { + GlyphContext_pushIndices(self); +} + +- (instancetype)initWithScale:(float)scale_ + width:(float)width + height:(float)height { + GlyphContext_initWithFloat_withFloat_withFloat_(self, scale_, width, height); + return self; +} + +- (void)reset { + GlyphContext_reset(self); +} + +- (FontData *)getFont { + return topFont_; +} + +- (FontData *)getTopOrParentFontWithRNSVGGroup:(RNSVGGroup*)child { + return GlyphContext_getTopOrParentFontWithRNSVGGroup_(self, child); +} + +- (void)pushNodeAndFontWithRNSVGGroup:(RNSVGGroup*)node + withNSDictionary:(NSDictionary*)font { + GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(self, node, font); +} + +- (void)pushContextWithRNSVGGroup:(RNSVGGroup*)node + font:(NSDictionary*)font { + GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(self, node, font); + GlyphContext_pushIndices(self); +} + +- (void)pushContextwithRNSVGText:(RNSVGText*)node + reset:(bool)reset + font:(NSDictionary*)font + x:(NSArray*)x + y:(NSArray*)y + deltaX:(NSArray*)deltaX + deltaY:(NSArray*)deltaY + rotate:(NSArray*)rotate { + if (reset) { + GlyphContext_reset(self); + } + GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(self, (RNSVGGroup*)node, font); + if (x != nil && [x count] != 0) { + mXsIndex_++; + mXIndex_ = -1; + [mXIndices_ addObject:[NSNumber numberWithInteger:mXIndex_]]; + mXs_ = x; + [mXsContext_ addObject:mXs_]; + } + if (y != nil && [y count] != 0) { + mYsIndex_++; + mYIndex_ = -1; + [mYIndices_ addObject:[NSNumber numberWithInteger:mYIndex_]]; + mYs_ = y; + [mYsContext_ addObject:mYs_]; + } + if (deltaX != nil && [deltaX count] != 0) { + mDXsIndex_++; + mDXIndex_ = -1; + [mDXIndices_ addObject:[NSNumber numberWithInteger:mDXIndex_]]; + mDXs_ = deltaX; + [mDXsContext_ addObject:mDXs_]; + } + if (deltaY != nil && [deltaY count] != 0) { + mDYsIndex_++; + mDYIndex_ = -1; + [mDYIndices_ addObject:[NSNumber numberWithInteger:mDYIndex_]]; + mDYs_ = deltaY; + [mDYsContext_ addObject:mDYs_]; + } + if (rotate != nil && [rotate count] != 0) { + mRsIndex_++; + mRIndex_ = -1; + [mRIndices_ addObject:[NSNumber numberWithInteger:mRIndex_]]; + mRs_ = rotate; + [mRsContext_ addObject:mRs_]; + } + GlyphContext_pushIndices(self); +} + +- (void)popContext { + [mFontContext_ removeLastObject]; + [mXsIndices_ removeLastObject]; + [mYsIndices_ removeLastObject]; + [mDXsIndices_ removeLastObject]; + [mDYsIndices_ removeLastObject]; + [mRsIndices_ removeLastObject]; + mTop_--; + int x = mXsIndex_; + int y = mYsIndex_; + int dx = mDXsIndex_; + int dy = mDYsIndex_; + int r = mRsIndex_; + topFont_ = [mFontContext_ lastObject]; + mXsIndex_ = [[mXsIndices_ lastObject] intValue]; + mYsIndex_ = [[mYsIndices_ lastObject] intValue]; + mDXsIndex_ = [[mDXsIndices_ lastObject] intValue]; + mDYsIndex_ = [[mDYsIndices_ lastObject] intValue]; + mRsIndex_ = [[mRsIndices_ lastObject] intValue]; + if (x != mXsIndex_) { + [mXsContext_ removeObjectAtIndex:x]; + mXs_ = [mXsContext_ objectAtIndex:mXsIndex_]; + mXIndex_ = [[mXIndices_ objectAtIndex:mXsIndex_] intValue]; + } + if (y != mYsIndex_) { + [mYsContext_ removeObjectAtIndex:y]; + mYs_ = [mYsContext_ objectAtIndex:mYsIndex_]; + mYIndex_ = [[mYIndices_ objectAtIndex:mYsIndex_] intValue]; + } + if (dx != mDXsIndex_) { + [mDXsContext_ removeObjectAtIndex:dx]; + mDXs_ = [mDXsContext_ objectAtIndex:mDXsIndex_]; + mDXIndex_ = [[mDXIndices_ objectAtIndex:mDXsIndex_] intValue]; + } + if (dy != mDYsIndex_) { + [mDYsContext_ removeObjectAtIndex:dy]; + mDYs_ = [mDYsContext_ objectAtIndex:mDYsIndex_]; + mDYIndex_ = [[mDYIndices_ objectAtIndex:mDYsIndex_] intValue]; + } + if (r != mRsIndex_) { + [mRsContext_ removeObjectAtIndex:r]; + mRs_ = [mRsContext_ objectAtIndex:mRsIndex_]; + mRIndex_ = [[mRIndices_ objectAtIndex:mRsIndex_] intValue]; + } +} + ++ (void)incrementIndicesWithNSArray:(NSMutableArray *)indices + withInt:(int)topIndex { + GlyphContext_incrementIndicesWithNSArray_withInt_(indices, topIndex); +} + +- (double)getFontSize { + return mFontSize_; +} + +- (double)nextXWithDouble:(double)advance { + GlyphContext_incrementIndicesWithNSArray_withInt_(mXIndices_, mXsIndex_); + int nextIndex = mXIndex_ + 1; + if (nextIndex < [mXs_ count]) { + mDX_ = 0; + mXIndex_ = nextIndex; + NSString *string = [mXs_ objectAtIndex:nextIndex]; + mX_ = [PropHelper fromRelativeWithNSString:string + relative:mWidth_ + offset:0 + scale:mScale_ + fontSize:mFontSize_]; + } + mX_ += advance; + return mX_; +} + +- (double)nextY { + GlyphContext_incrementIndicesWithNSArray_withInt_(mYIndices_, mYsIndex_); + int nextIndex = mYIndex_ + 1; + if (nextIndex < [mYs_ count]) { + mDY_ = 0; + mYIndex_ = nextIndex; + NSString *string = [mYs_ objectAtIndex:nextIndex]; + mY_ = [PropHelper fromRelativeWithNSString:string + relative:mHeight_ + offset:0 + scale:mScale_ + fontSize:mFontSize_]; + } + return mY_; +} + +- (double)nextDeltaX { + GlyphContext_incrementIndicesWithNSArray_withInt_(mDXIndices_, mDXsIndex_); + int nextIndex = mDXIndex_ + 1; + if (nextIndex < [mDXs_ count]) { + mDXIndex_ = nextIndex; + NSString *string = [mDXs_ objectAtIndex:nextIndex]; + double val = [PropHelper fromRelativeWithNSString:string + relative:mWidth_ + offset:0 + scale:mScale_ + fontSize:mFontSize_]; + mDX_ += val; + } + return mDX_; +} + +- (double)nextDeltaY { + GlyphContext_incrementIndicesWithNSArray_withInt_(mDYIndices_, mDYsIndex_); + int nextIndex = mDYIndex_ + 1; + if (nextIndex < [mDYs_ count]) { + mDYIndex_ = nextIndex; + NSString *string = [mDYs_ objectAtIndex:nextIndex]; + double val = [PropHelper fromRelativeWithNSString:string + relative:mHeight_ + offset:0 + scale:mScale_ + fontSize:mFontSize_]; + mDY_ += val; + } + return mDY_; +} + +- (NSNumber*)nextRotation { + GlyphContext_incrementIndicesWithNSArray_withInt_(mRIndices_, mRsIndex_); + int nextIndex = mRIndex_ + 1; + if (nextIndex < [mRs_ count]) { + mRIndex_ = nextIndex; + } + return mRs_[mRIndex_]; +} + +- (float)getWidth { + return mWidth_; +} + +- (float)getHeight { + return mHeight_; +} + +void GlyphContext_pushIndices(GlyphContext *self) { + [self->mXsIndices_ addObject:[NSNumber numberWithInteger:self->mXsIndex_]]; + [self->mYsIndices_ addObject:[NSNumber numberWithInteger:self->mYsIndex_]]; + [self->mDXsIndices_ addObject:[NSNumber numberWithInteger:self->mDXsIndex_]]; + [self->mDYsIndices_ addObject:[NSNumber numberWithInteger:self->mDYsIndex_]]; + [self->mRsIndices_ addObject:[NSNumber numberWithInteger:self->mRsIndex_]]; +} + +void GlyphContext_initWithFloat_withFloat_withFloat_(GlyphContext *self, float scale_, float width, float height) { + self->mFontContext_ = [[NSMutableArray alloc]init]; + self->mXsContext_ = [[NSMutableArray alloc]init]; + self->mYsContext_ = [[NSMutableArray alloc]init]; + self->mDXsContext_ = [[NSMutableArray alloc]init]; + self->mDYsContext_ = [[NSMutableArray alloc]init]; + self->mRsContext_ = [[NSMutableArray alloc]init]; + self->mXIndices_ = [[NSMutableArray alloc]init]; + self->mYIndices_ = [[NSMutableArray alloc]init]; + self->mDXIndices_ = [[NSMutableArray alloc]init]; + self->mDYIndices_ = [[NSMutableArray alloc]init]; + self->mRIndices_ = [[NSMutableArray alloc]init]; + self->mXsIndices_ = [[NSMutableArray alloc]init]; + self->mYsIndices_ = [[NSMutableArray alloc]init]; + self->mDXsIndices_ = [[NSMutableArray alloc]init]; + self->mDYsIndices_ = [[NSMutableArray alloc]init]; + self->mRsIndices_ = [[NSMutableArray alloc]init]; + self->mFontSize_ = FontData_DEFAULT_FONT_SIZE; + self->topFont_ = [FontData Defaults]; + self->mXs_ = [[NSArray alloc]init]; + self->mYs_ = [[NSArray alloc]init]; + self->mDXs_ = [[NSArray alloc]init]; + self->mDYs_ = [[NSArray alloc]init]; + self->mRs_ = [[NSArray alloc]initWithObjects:@0, nil]; + self->mXIndex_ = -1; + self->mYIndex_ = -1; + self->mDXIndex_ = -1; + self->mDYIndex_ = -1; + self->mRIndex_ = -1; + self->mScale_ = scale_; + self->mWidth_ = width; + self->mHeight_ = height; + [self->mXsContext_ addObject:self->mXs_]; + [self->mYsContext_ addObject:self->mYs_]; + [self->mDXsContext_ addObject:self->mDXs_]; + [self->mDYsContext_ addObject:self->mDYs_]; + [self->mRsContext_ addObject:self->mRs_]; + [self->mXIndices_ addObject:[NSNumber numberWithInteger:self->mXIndex_]]; + [self->mYIndices_ addObject:[NSNumber numberWithInteger:self->mYIndex_]]; + [self->mDXIndices_ addObject:[NSNumber numberWithInteger:self->mDXIndex_]]; + [self->mDYIndices_ addObject:[NSNumber numberWithInteger:self->mDYIndex_]]; + [self->mRIndices_ addObject:[NSNumber numberWithInteger:self->mRIndex_]]; + [self->mFontContext_ addObject:self->topFont_]; + GlyphContext_pushIndices(self); +} + +void GlyphContext_reset(GlyphContext *self) { + self->mXsIndex_ = self->mYsIndex_ = self->mDXsIndex_ = self->mDYsIndex_ = self->mRsIndex_ = 0; + self->mXIndex_ = self->mYIndex_ = self->mDXIndex_ = self->mDYIndex_ = self->mRIndex_ = -1; + self->mX_ = self->mY_ = self->mDX_ = self->mDY_ = 0; +} + +FontData *GlyphContext_getTopOrParentFontWithRNSVGGroup_(GlyphContext *self, RNSVGGroup* child) { + if (self->mTop_ > 0) { + return self->topFont_; + } + else { + RNSVGGroup* parentRoot = [child getParentTextRoot]; + FontData* Defaults = [FontData Defaults]; + while (parentRoot != nil) { + FontData *map = [[parentRoot getGlyphContext] getFont]; + if (map != Defaults) { + return map; + } + parentRoot = [parentRoot getParentTextRoot]; + } + return Defaults; + } +} + +void GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(GlyphContext *self, RNSVGGroup* node, NSDictionary* font) { + FontData *parent = GlyphContext_getTopOrParentFontWithRNSVGGroup_(self, node); + self->mTop_++; + if (font == nil) { + [self->mFontContext_ addObject:parent]; + return; + } + FontData *data = [FontData initWithNSDictionary:font + parent:parent + scale:self->mScale_]; + self->mFontSize_ = data->fontSize; + [self->mFontContext_ addObject:data]; + self->topFont_ = data; +} + +void GlyphContext_incrementIndicesWithNSArray_withInt_(NSMutableArray *indices, int topIndex) { + for (int index = topIndex; index >= 0; index--) { + int xIndex = [[indices objectAtIndex:index] intValue]; + [indices setObject:[NSNumber numberWithInteger:xIndex + 1] atIndexedSubscript:index]; + } +} +@end diff --git a/ios/Text/PropHelper.h b/ios/Text/PropHelper.h new file mode 100644 index 000000000..37d808a26 --- /dev/null +++ b/ios/Text/PropHelper.h @@ -0,0 +1,15 @@ +#import +#import + +#if !defined (PropHelper_) +#define PropHelper_ +@interface PropHelper : NSObject + ++ (double) fromRelativeWithNSString:(NSString *)length + relative:(double)relative + offset:(double)offset + scale:(double)scale + fontSize:(double)fontSize; + +@end +#endif diff --git a/ios/Text/PropHelper.m b/ios/Text/PropHelper.m new file mode 100644 index 000000000..941039555 --- /dev/null +++ b/ios/Text/PropHelper.m @@ -0,0 +1,49 @@ +#include "PropHelper.h" +@implementation PropHelper + ++ (double)fromRelativeWithNSString:(NSString *)length + relative:(double)relative + offset:(double)offset + scale:(double)scale + fontSize:(double)fontSize { + length = [length stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSUInteger stringLength = [length length]; + NSInteger percentIndex = stringLength - 1; + if (stringLength == 0) { + return offset; + } + else if ([length characterAtIndex:percentIndex] == '%') { + return [[length substringWithRange:NSMakeRange(0, percentIndex)] doubleValue] / 100 * relative + offset; + } + else { + NSInteger twoLetterUnitIndex = stringLength - 2; + if (twoLetterUnitIndex > 0) { + NSString *lastTwo = [length substringFromIndex:twoLetterUnitIndex]; + NSUInteger end = twoLetterUnitIndex; + double unit = 1; + if ([lastTwo isEqualToString:@"px"]) { + + } else if ([lastTwo isEqualToString:@"em"]) { + unit = fontSize; + } else if ([lastTwo isEqualToString:@"pt"]) { + unit = 1.25; + } else if ([lastTwo isEqualToString:@"pc"]) { + unit = 15; + } else if ([lastTwo isEqualToString:@"mm"]) { + unit = 3.543307; + } else if ([lastTwo isEqualToString:@"cm"]) { + unit = 35.43307; + } else if ([lastTwo isEqualToString:@"in"]) { + unit = 90; + } else { + end = stringLength; + } + + return [[length substringWithRange:NSMakeRange(0, end)] doubleValue] * unit * scale + offset; + } else { + return [length doubleValue] * scale + offset; + } + } +} + +@end diff --git a/ios/Text/RNSVGTSpan.h b/ios/Text/RNSVGTSpan.h index 3e04fc0b6..0f15645bf 100644 --- a/ios/Text/RNSVGTSpan.h +++ b/ios/Text/RNSVGTSpan.h @@ -5,10 +5,17 @@ * This source code is licensed under the MIT-style license found in the * LICENSE file in the root directory of this source tree. */ - +#import +#import #import #import #import "RNSVGText.h" +#import "TextPathSide.h" +#import "TextPathMethod.h" +#import "TextPathMidLine.h" +#import "TextPathSpacing.h" +#import "TextLengthAdjust.h" +#import "AlignmentBaseline.h" @interface RNSVGTSpan : RNSVGText diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 4aef6bfae..d5f8edd6f 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -5,17 +5,23 @@ * This source code is licensed under the MIT-style license found in the * LICENSE file in the root directory of this source tree. */ - - +#import #import "RNSVGTSpan.h" #import "RNSVGBezierTransformer.h" #import "RNSVGText.h" #import "RNSVGTextPath.h" +#import "UIBezierPath+Text.h" +#import "FontData.h" @implementation RNSVGTSpan { RNSVGBezierTransformer *_bezierTransformer; + CGFloat startOffset; + UIBezierPath *_path; CGPathRef _cache; + CGFloat pathLength; + RNSVGTextPath * textPath; + RNSVGPath * textPathPath; } - (void)setContent:(NSString *)content @@ -63,9 +69,6 @@ - (CGPathRef)getPath:(CGContextRef)context CGMutablePathRef path = CGPathCreateMutable(); - // append spacing - text = [text stringByAppendingString:@" "]; - [self pushGlyphContext]; CTFontRef font = [self getFontFromContext]; @@ -73,9 +76,9 @@ - (CGPathRef)getPath:(CGContextRef)context CFDictionaryRef attributes; if (font != nil) { attributes = (__bridge CFDictionaryRef)@{ - (NSString *)kCTFontAttributeName: (__bridge id)font, - (NSString *)kCTForegroundColorFromContextAttributeName: @YES - }; + (NSString *)kCTFontAttributeName: (__bridge id)font, + (NSString *)kCTForegroundColorFromContextAttributeName: @YES + }; } else { attributes = (__bridge CFDictionaryRef)@{ (NSString *)kCTForegroundColorFromContextAttributeName: @YES @@ -86,8 +89,8 @@ - (CGPathRef)getPath:(CGContextRef)context CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CTLineRef line = CTLineCreateWithAttributedString(attrString); - CGMutablePathRef linePath = [self getLinePath:line]; - CGAffineTransform offset = CGAffineTransformMakeTranslation(0, _bezierTransformer ? 0 : CTFontGetSize(font) * 1.1); + CGMutablePathRef linePath = [self getLinePath:line text:text font:font]; + CGAffineTransform offset = CGAffineTransformIdentity; CGPathAddPath(path, &offset, linePath); CGPathRelease(linePath); @@ -101,11 +104,585 @@ - (CGPathRef)getPath:(CGContextRef)context return (CGPathRef)CFAutorelease(path); } -- (CGMutablePathRef)getLinePath:(CTLineRef)line +- (CGMutablePathRef)getLinePath:(CTLineRef)line text:(NSString *)str font:(CTFontRef)fontRef { CGMutablePathRef path = CGPathCreateMutable(); CFArrayRef runs = CTLineGetGlyphRuns(line); - for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) { + GlyphContext* gc = [[self getTextRoot] getGlyphContext]; + FontData* font = [gc getFont]; + NSUInteger length = str.length; + NSUInteger n = length; + unichar characters[n]; + CFStringGetCharacters((__bridge CFStringRef) str, CFRangeMake(0, n), characters); + CGGlyph glyphs[n]; + CGSize glyph_advances[n]; + + CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, n); + CTFontGetAdvancesForGlyphs(fontRef, kCTFontDefaultOrientation, glyphs, glyph_advances, n); + /* + * + * Three properties affect the space between characters and words: + * + * ‘kerning’ indicates whether the user agent should adjust inter-glyph spacing + * based on kerning tables that are included in the relevant font + * (i.e., enable auto-kerning) or instead disable auto-kerning + * and instead set inter-character spacing to a specific length (typically, zero). + * + * ‘letter-spacing’ indicates an amount of space that is to be added between text + * characters supplemental to any spacing due to the ‘kerning’ property. + * + * ‘word-spacing’ indicates the spacing behavior between words. + * + * Letter-spacing is applied after bidi reordering and is in addition to any word-spacing. + * Depending on the justification rules in effect, user agents may further increase + * or decrease the space between typographic character units in order to justify text. + * + * */ + // TODO double kerning = font->kerning; + double wordSpacing = font->wordSpacing; + double letterSpacing = font->letterSpacing; + bool autoKerning = !font->manualKerning; + + /* + 11.1.2. Fonts and glyphs + + A font consists of a collection of glyphs together with other information (collectively, + the font tables) necessary to use those glyphs to present characters on some visual medium. + + The combination of the collection of glyphs and the font tables is called the font data. + + A font may supply substitution and positioning tables that can be used by a formatter + (text shaper) to re-order, combine and position a sequence of glyphs to form one or more + composite glyphs. + + The combining may be as simple as a ligature, or as complex as an indic syllable which + combines, usually with some re-ordering, multiple consonants and vowel glyphs. + + The tables may be language dependent, allowing the use of language appropriate letter forms. + + When a glyph, simple or composite, represents an indivisible unit for typesetting purposes, + it is know as a typographic character. + + Ligatures are an important feature of advance text layout. + + Some ligatures are discretionary while others (e.g. in Arabic) are required. + + The following explicit rules apply to ligature formation: + + Ligature formation should not be enabled when characters are in different DOM text nodes; + thus, characters separated by markup should not use ligatures. + + Ligature formation should not be enabled when characters are in different text chunks. + + Discretionary ligatures should not be used when the spacing between two characters is not + the same as the default space (e.g. when letter-spacing has a non-default value, + or text-align has a value of justify and text-justify has a value of distribute). + (See CSS Text Module Level 3, ([css-text-3]). + + SVG attributes such as ‘dx’, ‘textLength’, and ‘spacing’ (in ‘textPath’) that may reposition + typographic characters do not break discretionary ligatures. + + If discretionary ligatures are not desired + they can be turned off by using the font-variant-ligatures property. + + When the effective letter-spacing between two characters is not zero + (due to either justification or non-zero computed ‘letter-spacing’), + user agents should not apply optional ligatures. + https://www.w3.org/TR/css-text-3/#letter-spacing-property + */ + // TODO bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == FontVariantLigaturesNormal; + + /* + For OpenType fonts, discretionary ligatures include those enabled by + the liga, clig, dlig, hlig, and cala features; + required ligatures are found in the rlig feature. + https://svgwg.org/svg2-draft/text.html#FontsGlyphs + + http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings + + https://www.microsoft.com/typography/otspec/featurelist.htm + https://www.microsoft.com/typography/otspec/featuretags.htm + https://www.microsoft.com/typography/otspec/features_pt.htm + https://www.microsoft.com/typography/otfntdev/arabicot/features.aspx + http://unifraktur.sourceforge.net/testcases/enable_opentype_features/ + https://en.wikipedia.org/wiki/List_of_typographic_features + http://ilovetypography.com/OpenType/opentype-features.html + https://www.typotheque.com/articles/opentype_features_in_css + https://practice.typekit.com/lesson/caring-about-opentype-features/ + http://stateofwebtype.com/ + + 6.12. Low-level font feature settings control: the font-feature-settings property + + Name: font-feature-settings + Value: normal | # + Initial: normal + Applies to: all elements + Inherited: yes + Percentages: N/A + Media: visual + Computed value: as specified + Animatable: no + + https://drafts.csswg.org/css-fonts-3/#default-features + + 7.1. Default features + + For OpenType fonts, user agents must enable the default features defined in the OpenType + documentation for a given script and writing mode. + + Required ligatures, common ligatures and contextual forms must be enabled by default + (OpenType features: rlig, liga, clig, calt), + along with localized forms (OpenType feature: locl), + and features required for proper display of composed characters and marks + (OpenType features: ccmp, mark, mkmk). + + These features must always be enabled, even when the value of the ‘font-variant’ and + ‘font-feature-settings’ properties is ‘normal’. + + Individual features are only disabled when explicitly overridden by the author, + as when ‘font-variant-ligatures’ is set to ‘no-common-ligatures’. + + TODO For handling complex scripts such as Arabic, Mongolian or Devanagari additional features + are required. + + TODO For upright text within vertical text runs, + vertical alternates (OpenType feature: vert) must be enabled. + */ + // OpenType.js font data + // TODO NSDictionary * fontData = font->fontData; + + /* + float advances[] = new float[length]; + paint.getTextWidths(line, advances); + */ + + /* + This would give both advances and textMeasure in one call / looping over the text + double textMeasure = paint.getTextRunAdvances(line, 0, length, 0, length, true, advances, 0); + */ + /* + Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ + and property text-anchor. + + For text-anchor:start, startpoint-on-the-path is the point + on the path which represents the point on the path which is ‘startOffset’ distance + along the path from the start of the path, calculated using the user agent's distance + along the path algorithm. + + For text-anchor:middle, startpoint-on-the-path is the point + on the path which represents the point on the path which is [ ‘startOffset’ minus half + of the total advance values for all of the glyphs in the ‘textPath’ element ] distance + along the path from the start of the path, calculated using the user agent's distance + along the path algorithm. + + For text-anchor:end, startpoint-on-the-path is the point on + the path which represents the point on the path which is [ ‘startOffset’ minus the + total advance values for all of the glyphs in the ‘textPath’ element ]. + + Before rendering the first glyph, the horizontal component of the startpoint-on-the-path + is adjusted to take into account various horizontal alignment text properties and + attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. + */ + enum TextAnchor textAnchor = font->textAnchor; + CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); + double textMeasure = CGRectGetWidth(textBounds); + double offset = getTextAnchorOffset(textAnchor, textMeasure); + + bool hasTextPath = _path != nil; + bool isClosed = false; + + int side = 1; + double startOfRendering = 0; + double endOfRendering = pathLength; + double fontSize = [gc getFontSize]; + bool sharpMidLine = false; + if (hasTextPath) { + sharpMidLine = TextPathMidLineFromString([textPath midLine]) == TextPathMidLineSharp; + /* + Name + side + Value + left | right + initial value + left + Animatable + yes + + Determines the side of the path the text is placed on + (relative to the path direction). + + Specifying a value of right effectively reverses the path. + + Added in SVG 2 to allow text either inside or outside closed subpaths + and basic shapes (e.g. rectangles, circles, and ellipses). + + Adding 'side' was resolved at the Sydney (2015) meeting. + */ + side = TextPathSideFromString([textPath side]) == TextPathSideRight ? -1 : 1; + /* + Name + startOffset + Value + | | + initial value + 0 + Animatable + yes + + An offset from the start of the path for the initial current text position, + calculated using the user agent's distance along the path algorithm, + after converting the path to the ‘textPath’ element's coordinate system. + + If a other than a percentage is given, then the ‘startOffset’ + represents a distance along the path measured in the current user coordinate + system for the ‘textPath’ element. + + If a percentage is given, then the ‘startOffset’ represents a percentage + distance along the entire path. Thus, startOffset="0%" indicates the start + point of the path and startOffset="100%" indicates the end point of the path. + + Negative values and values larger than the path length (e.g. 150%) are allowed. + + Any typographic characters with mid-points that are not on the path are not rendered + + For paths consisting of a single closed subpath (including an equivalent path for a + basic shape), typographic characters are rendered along one complete circuit of the + path. The text is aligned as determined by the text-anchor property to a position + along the path set by the ‘startOffset’ attribute. + + For the start (end) value, the text is rendered from the start (end) of the line + until the initial position along the path is reached again. + + For the middle, the text is rendered from the middle point in both directions until + a point on the path equal distance in both directions from the initial position on + the path is reached. + */ + double absoluteStartOffset = [PropHelper fromRelativeWithNSString:textPath.startOffset + relative:pathLength + offset:0 + scale:1 + fontSize:fontSize]; + offset += absoluteStartOffset; + if (isClosed) { + double halfPathDistance = pathLength / 2; + startOfRendering = absoluteStartOffset + (textAnchor == TextAnchorMiddle ? -halfPathDistance : 0); + endOfRendering = startOfRendering + pathLength; + } + /* + TextPathSpacing spacing = textPath.getSpacing(); + if (spacing == TextPathSpacing.auto) { + // Hmm, what to do here? + // https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute + } + */ + } + + /* + Name + method + Value + align | stretch + initial value + align + Animatable + yes + Indicates the method by which text should be rendered along the path. + + A value of align indicates that the typographic character should be rendered using + simple 2×3 matrix transformations such that there is no stretching/warping of the + typographic characters. Typically, supplemental rotation, scaling and translation + transformations are done for each typographic characters to be rendered. + + As a result, with align, in fonts where the typographic characters are designed to be + connected (e.g., cursive fonts), the connections may not align properly when text is + rendered along a path. + + A value of stretch indicates that the typographic character outlines will be converted + into paths, and then all end points and control points will be adjusted to be along the + perpendicular vectors from the path, thereby stretching and possibly warping the glyphs. + + With this approach, connected typographic characters, such as in cursive scripts, + will maintain their connections. (Non-vertical straight path segments should be + converted to Bézier curves in such a way that horizontal straight paths have an + (approximately) constant offset from the path along which the typographic characters + are rendered.) + + TODO implement stretch + */ + + /* + Name Value Initial value Animatable + textLength | | See below yes + + The author's computation of the total sum of all of the advance values that correspond + to character data within this element, including the advance value on the glyph + (horizontal or vertical), the effect of properties letter-spacing and word-spacing and + adjustments due to attributes ‘dx’ and ‘dy’ on this ‘text’ or ‘tspan’ element or any + descendants. This value is used to calibrate the user agent's own calculations with + that of the author. + + The purpose of this attribute is to allow the author to achieve exact alignment, + in visual rendering order after any bidirectional reordering, for the first and + last rendered glyphs that correspond to this element; thus, for the last rendered + character (in visual rendering order after any bidirectional reordering), + any supplemental inter-character spacing beyond normal glyph advances are ignored + (in most cases) when the user agent determines the appropriate amount to expand/compress + the text string to fit within a length of ‘textLength’. + + If attribute ‘textLength’ is specified on a given element and also specified on an + ancestor, the adjustments on all character data within this element are controlled by + the value of ‘textLength’ on this element exclusively, with the possible side-effect + that the adjustment ratio for the contents of this element might be different than the + adjustment ratio used for other content that shares the same ancestor. The user agent + must assume that the total advance values for the other content within that ancestor is + the difference between the advance value on that ancestor and the advance value for + this element. + + This attribute is not intended for use to obtain effects such as shrinking or + expanding text. + + A negative value is an error (see Error processing). + + The ‘textLength’ attribute is only applied when the wrapping area is not defined by the + TODO shape-inside or the inline-size properties. It is also not applied for any ‘text’ or + TODO ‘tspan’ element that has forced line breaks (due to a white-space value of pre or + pre-line). + + If the attribute is not specified anywhere within a ‘text’ element, the effect is as if + the author's computation exactly matched the value calculated by the user agent; + thus, no advance adjustments are made. + */ + double scaleSpacingAndGlyphs = 1; + NSString *mTextLength = [self textLength]; + enum TextLengthAdjust mLengthAdjust = TextLengthAdjustFromString([self lengthAdjust]); + // TODO canvasWidth + double canvasWidth = 0; + if (mTextLength != nil) { + double author = [PropHelper fromRelativeWithNSString:mTextLength + relative:canvasWidth + offset:0 + scale:1 + fontSize:fontSize]; + if (author < 0) { + NSException *e = [NSException + exceptionWithName:@"NegativeTextLength" + reason:@"Negative textLength value" + userInfo:nil]; + @throw e; + } + switch (mLengthAdjust) { + default: + case TextLengthAdjustSpacing: + letterSpacing += (author - textMeasure) / (length - 1); + break; + case TextLengthAdjustSpacingAndGlyphs: + scaleSpacingAndGlyphs = author / textMeasure; + break; + } + } + double scaledDirection = scaleSpacingAndGlyphs * side; + + /* + https://developer.mozilla.org/en/docs/Web/CSS/vertical-align + https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html + https://www.microsoft.com/typography/otspec/base.htm + http://apike.ca/prog_svg_text_style.html + https://www.w3schools.com/tags/canvas_textbaseline.asp + http://vanseodesign.com/web-design/svg-text-baseline-alignment/ + https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align + https://tympanus.net/codrops/css_reference/vertical-align/ + + https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty + 11.10.2.6. The ‘alignment-baseline’ property + + This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3] + https://drafts.csswg.org/css-inline/#propdef-alignment-baseline + + The vertical-align property shorthand should be preferred in new content. + + SVG 2 introduces some changes to the definition of this property. + In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed. + For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and + 'text-after-edge' should be mapped to 'text-bottom'. + + Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. + */ + //Paint.FontMetrics fm = paint.getFontMetrics(); + double top = 0;//-fm.top; + double bottom = 0;//fm.bottom; + double ascenderHeight = 0;//-fm.ascent; + double descenderDepth = 0;//fm.descent; + double totalHeight = top + bottom; + double baselineShift = 0; + NSString *baselineShiftString = [self baselineShift]; + enum AlignmentBaseline baseline = AlignmentBaselineFromString([self alignmentBaseline]); + if (baseline != AlignmentBaselineBaseline) { + // TODO alignment-baseline, test / verify behavior + // TODO get per glyph baselines from font baseline table, for high-precision alignment + switch (baseline) { + // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + default: + case AlignmentBaselineBaseline: + // Use the dominant baseline choice of the parent. + // Match the box’s corresponding baseline to that of its parent. + baselineShift = 0; + break; + + case AlignmentBaselineTextBottom: + case AlignmentBaselineAfterEdge: + case AlignmentBaselineTextAfterEdge: + // Match the bottom of the box to the bottom of the parent’s content area. + // text-after-edge = text-bottom + // text-after-edge = descender depth + baselineShift = -descenderDepth; + break; + + case AlignmentBaselineAlphabetic: + // Match the box’s alphabetic baseline to that of its parent. + // alphabetic = 0 + baselineShift = 0; + break; + + case AlignmentBaselineIdeographic: + // Match the box’s ideographic character face under-side baseline to that of its parent. + // ideographic = descender depth + baselineShift = -descenderDepth; + break; + + case AlignmentBaselineMiddle: + // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. TODO + // middle = x height / 2 + //Rect bounds = new Rect(); + // this will just retrieve the bounding rect for 'x' + //paint.getTextBounds("x", 0, 1, bounds); + //int xHeight = bounds.height(); + //baselineShift = xHeight / 2; + break; + + case AlignmentBaselineCentral: + // Match the box’s central baseline to the central baseline of its parent. + // central = (ascender height - descender depth) / 2 + baselineShift = (ascenderHeight - descenderDepth) / 2; + break; + + case AlignmentBaselineMathematical: + // Match the box’s mathematical baseline to that of its parent. + // Hanging and mathematical baselines + // There are no obvious formulas to calculate the position of these baselines. + // At the time of writing FOP puts the hanging baseline at 80% of the ascender + // height and the mathematical baseline at 50%. + baselineShift = 0.5 * ascenderHeight; + break; + + case AlignmentBaselineHanging: + baselineShift = 0.8 * ascenderHeight; + break; + + case AlignmentBaselineTextTop: + case AlignmentBaselineBeforeEdge: + case AlignmentBaselineTextBeforeEdge: + // Match the top of the box to the top of the parent’s content area. + // text-before-edge = text-top + // text-before-edge = ascender height + baselineShift = ascenderHeight; + break; + + case AlignmentBaselineBottom: + // Align the top of the aligned subtree with the top of the line box. + baselineShift = bottom; + break; + + case AlignmentBaselineCenter: + // Align the center of the aligned subtree with the center of the line box. + baselineShift = totalHeight / 2; + break; + + case AlignmentBaselineTop: + // Align the bottom of the aligned subtree with the bottom of the line box. + baselineShift = top; + break; + } + /* + 2.2.2. Alignment Shift: baseline-shift longhand + + This property specifies by how much the box is shifted up from its alignment point. + It does not apply when alignment-baseline is top or bottom. + + Authors should use the vertical-align shorthand instead of this property. + + Values have the following meanings: + + + Raise (positive value) or lower (negative value) by the specified length. + + Raise (positive value) or lower (negative value) by the specified percentage of the line-height. + TODO sub + Lower by the offset appropriate for subscripts of the parent’s box. + (The UA should use the parent’s font data to find this offset whenever possible.) + TODO super + Raise by the offset appropriate for superscripts of the parent’s box. + (The UA should use the parent’s font data to find this offset whenever possible.) + + User agents may additionally support the keyword baseline as computing to 0 + if is necessary for them to support legacy SVG content. + Issue: We would prefer to remove this, + and are looking for feedback from SVG user agents as to whether it’s necessary. + + https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift + */ + if (baselineShiftString != nil) { + switch (baseline) { + case AlignmentBaselineTop: + case AlignmentBaselineBottom: + break; + + default: + if ([baselineShiftString isEqualToString:@"sub"]) { + // TODO + /* + if (fontData != nil && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) { + int unitsPerEm = fontData.getInt("unitsPerEm"); + ReadableMap tables = fontData.getMap("tables"); + if (tables.hasKey("os2")) { + ReadableMap os2 = tables.getMap("os2"); + if (os2.hasKey("ySubscriptYOffset")) { + double subOffset = os2.getDouble("ySubscriptYOffset"); + baselineShift += fontSize * subOffset / unitsPerEm; + } + } + } + */ + } else if ([baselineShiftString isEqualToString:@"super"]) { + // TODO + /* + if (fontData != nil && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) { + int unitsPerEm = fontData.getInt("unitsPerEm"); + ReadableMap tables = fontData.getMap("tables"); + if (tables.hasKey("os2")) { + ReadableMap os2 = tables.getMap("os2"); + if (os2.hasKey("ySuperscriptYOffset")) { + double superOffset = os2.getDouble("ySuperscriptYOffset"); + baselineShift -= fontSize * superOffset / unitsPerEm; + } + } + } + */ + } else if ([baselineShiftString isEqualToString:@"baseline"]) { + } else { + baselineShift -= [PropHelper fromRelativeWithNSString:baselineShiftString + relative:fontSize + offset:0 + scale:1 + fontSize:fontSize]; + + } + break; + } + } + } + + CFIndex runEnd = CFArrayGetCount(runs); + for (CFIndex i = 0; i < runEnd; i++) { CTRunRef run = CFArrayGetValueAtIndex(CTLineGetGlyphRuns(line), i); CFIndex runGlyphCount = CTRunGetGlyphCount(run); @@ -117,31 +694,89 @@ - (CGMutablePathRef)getLinePath:(CTLineRef)line CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); - CGPoint glyphPoint; - for(CFIndex i = 0; i < runGlyphCount; i++) { CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); - glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))]; + /* + Determine the glyph's charwidth (i.e., the amount which the current text position + advances horizontally when the glyph is drawn using horizontal text layout). + */ + CGFloat charWidth = glyph_advances[i].width * scaleSpacingAndGlyphs; + //CGPoint glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))]; + + /* + For each subsequent glyph, set a new startpoint-on-the-path as the previous + endpoint-on-the-path, but with appropriate adjustments taking into account + horizontal kerning tables in the font and current values of various attributes + and properties, including spacing properties (e.g. letter-spacing and word-spacing) + and ‘tspan’ elements with values provided for attributes ‘dx’ and ‘dy’. All + adjustments are calculated as distance adjustments along the path, calculated + using the user agent's distance along the path algorithm. + */ + if (autoKerning) { + // TODO double kerned = advances[index] * scaleSpacingAndGlyphs; + //kerning = kerned - charWidth; + } - CGAffineTransform textPathTransform = CGAffineTransformIdentity; - CGAffineTransform transform; - if (_bezierTransformer) { - textPathTransform = [_bezierTransformer getTransformAtDistance:glyphPoint.x]; - if ([self textPathHasReachedEnd]) { - CGPathRelease(letter); - break; - } else if (![self textPathHasReachedStart]) { - CGPathRelease(letter); + bool isWordSeparator = false; + double wordSpace = isWordSeparator ? wordSpacing : 0; + double spacing = wordSpace + letterSpacing; + double advance = charWidth + spacing; + + double x = [gc nextXWithDouble:charWidth]; + double y = [gc nextY]; + double dx = [gc nextDeltaX]; + double dy = [gc nextDeltaY]; + NSNumber* r = [gc nextRotation]; + + advance *= side; + charWidth *= side; + double cursor = offset + (x + dx) * side; + double startPoint = cursor - advance; + + CGAffineTransform transform = CGAffineTransformIdentity; + if (hasTextPath) { + /* + Determine the point on the curve which is charwidth distance along the path from + the startpoint-on-the-path for this glyph, calculated using the user agent's + distance along the path algorithm. This point is the endpoint-on-the-path for + the glyph. + */ + // TODO double endPoint = startPoint + charWidth; + + /* + Determine the midpoint-on-the-path, which is the point on the path which is + "halfway" (user agents can choose either a distance calculation or a parametric + calculation) between the startpoint-on-the-path and the endpoint-on-the-path. + */ + double halfWay = charWidth / 2; + double midPoint = startPoint + halfWay; + + // Glyphs whose midpoint-on-the-path are off the path are not rendered. + if (midPoint > endOfRendering) { + continue; + } else if (midPoint < startOfRendering) { continue; } - textPathTransform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, glyphPoint.y), textPathTransform); - transform = CGAffineTransformScale(textPathTransform, 1.0, -1.0); + CGPoint slope; + CGFloat percentConsumed = midPoint / pathLength; + CGPoint mid = [_path pointAtPercent:percentConsumed withSlope:&slope]; + // Calculate the rotation + double angle = atan(slope.y / slope.x); // + M_PI; + if (slope.x < 0) angle += M_PI; // going left, update the angle + + transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(mid.x, mid.y), transform); + transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(angle + [r doubleValue]), transform); + transform = CGAffineTransformScale(transform, scaledDirection, side); + transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-halfWay, dy + baselineShift), transform); + transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, y), transform); } else { - transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(1.0, -1.0), glyphPoint.x, -glyphPoint.y); + transform = CGAffineTransformConcat(CGAffineTransformMakeRotation([r doubleValue]), transform); + transform = CGAffineTransformMakeTranslation(startPoint, y + dy); } + transform = CGAffineTransformScale(transform, 1.0, -1.0); CGPathAddPath(path, &transform, letter); CGPathRelease(letter); } @@ -151,17 +786,37 @@ - (CGMutablePathRef)getLinePath:(CTLineRef)line return path; } +CGFloat getTextAnchorOffset(enum TextAnchor textAnchor, CGFloat width) +{ + switch (textAnchor) { + case TextAnchorStart: + return 0; + case TextAnchorMiddle: + return -width / 2; + case TextAnchorEnd: + return -width; + } + + return 0; +} + - (void)setupTextPath:(CGContextRef)context { + _path = nil; + textPath = nil; + textPathPath = nil; __block RNSVGBezierTransformer *bezierTransformer; [self traverseTextSuperviews:^(__kindof RNSVGText *node) { if ([node class] == [RNSVGTextPath class]) { - bezierTransformer = [(RNSVGTextPath*)node getBezierTransformer]; + textPath = (RNSVGTextPath*) node; + bezierTransformer = [textPath getBezierTransformer]; + textPathPath = [textPath getPath]; + _path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; + pathLength = [_path length]; return NO; } return YES; }]; - _bezierTransformer = bezierTransformer; } diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index aac064a6f..b977fc40a 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -12,15 +12,18 @@ @interface RNSVGText : RNSVGGroup -@property (nonatomic, assign) RNSVGTextAnchor textAnchor; +@property (nonatomic, strong) NSString *textLength; +@property (nonatomic, strong) NSString *baselineShift; +@property (nonatomic, strong) NSString *lengthAdjust; +@property (nonatomic, strong) NSString *alignmentBaseline; @property (nonatomic, strong) NSArray *deltaX; @property (nonatomic, strong) NSArray *deltaY; @property (nonatomic, strong) NSArray *positionX; @property (nonatomic, strong) NSArray *positionY; +@property (nonatomic, strong) NSArray *rotate; - (void)releaseCachedPath; - (CGPathRef)getGroupPath:(CGContextRef)context; - - (CTFontRef)getFontFromContext; - (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth; diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 31fae9fac..cf94e2408 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -11,17 +11,13 @@ #import #import #import "RNSVGGlyphContext.h" +#import "GlyphContext.h" @implementation RNSVGText { RNSVGText *_textRoot; - RNSVGGlyphContext *_glyphContext; -} - -- (void)setTextAnchor:(RNSVGTextAnchor)textAnchor -{ - [self invalidate]; - _textAnchor = textAnchor; + GlyphContext *_glyphContext; + RNSVGGlyphContext *_RNSVGGlyphContext; } - (void)renderLayerTo:(CGContextRef)context @@ -31,21 +27,20 @@ - (void)renderLayerTo:(CGContextRef)context [self setupGlyphContext:context]; CGPathRef path = [self getGroupPath:context]; - CGAffineTransform transform = [self getAlignTransform:path]; - CGContextConcatCTM(context, transform); [self renderGroupTo:context]; [self releaseCachedPath]; CGContextRestoreGState(context); - - CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &transform); + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &CGAffineTransformIdentity); [self setHitArea:transformedPath]; CGPathRelease(transformedPath); } - (void)setupGlyphContext:(CGContextRef)context { - _glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] + _glyphContext = [[GlyphContext alloc] initWithScale:1 width:[self getContextWidth] + height:[self getContextHeight]]; + _RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] height:[self getContextHeight]]; } @@ -72,10 +67,9 @@ - (CGPathRef)getPath:(CGContextRef)context { [self setupGlyphContext:context]; CGPathRef groupPath = [self getGroupPath:context]; - CGAffineTransform transform = [self getAlignTransform:groupPath]; [self releaseCachedPath]; - return (CGPathRef)CFAutorelease(CGPathCreateCopyByTransformingPath(groupPath, &transform)); + return (CGPathRef)CFAutorelease(CGPathCreateCopyByTransformingPath(groupPath, &CGAffineTransformIdentity)); } - (void)renderGroupTo:(CGContextRef)context @@ -85,38 +79,6 @@ - (void)renderGroupTo:(CGContextRef)context [self popGlyphContext]; } -- (CGAffineTransform)getAlignTransform:(CGPathRef)path -{ - CGFloat width = CGRectGetWidth(CGPathGetBoundingBox(path)); - CGFloat x = 0; - - switch ([self getComputedTextAnchor]) { - case kRNSVGTextAnchorMiddle: - x = -width / 2; - break; - case kRNSVGTextAnchorEnd: - x = -width; - break; - default: ; - } - - return CGAffineTransformMakeTranslation(x, 0); -} - -- (RNSVGTextAnchor)getComputedTextAnchor -{ - RNSVGTextAnchor anchor = self.textAnchor; - if (self.subviews.count > 0) { - RNSVGText *child = [self.subviews firstObject]; - - while (child.subviews.count && anchor == kRNSVGTextAnchorAuto) { - anchor = child.textAnchor; - child = [child.subviews firstObject]; - } - } - return anchor; -} - - (RNSVGText *)getTextRoot { if (!_textRoot) { @@ -133,18 +95,31 @@ - (RNSVGText *)getTextRoot return _textRoot; } -- (RNSVGGlyphContext *)getGlyphContext +- (RNSVGGlyphContext *)getRNSVGGlyphContext +{ + return _RNSVGGlyphContext; +} + +- (GlyphContext *)getGlyphContext { return _glyphContext; } - (void)pushGlyphContext { - [[[self getTextRoot] getGlyphContext] pushContext:self.font - deltaX:self.deltaX - deltaY:self.deltaY - positionX:self.positionX - positionY:self.positionY]; + [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font + deltaX:self.deltaX + deltaY:self.deltaY + positionX:self.positionX + positionY:self.positionY]; + [[[self getTextRoot] getGlyphContext] pushContextwithRNSVGText:self + reset:false + font:self.font + x:self.positionX + y:self.positionY + deltaX:self.deltaX + deltaY:self.deltaY + rotate:self.rotate]; } - (void)popGlyphContext @@ -159,7 +134,7 @@ - (CTFontRef)getFontFromContext - (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth { - return [[[self getTextRoot] getGlyphContext] getNextGlyphPoint:(CGPoint)offset glyphWidth:glyphWidth]; + return [[[self getTextRoot] getRNSVGGlyphContext] getNextGlyphPoint:(CGPoint)offset glyphWidth:glyphWidth]; } @end diff --git a/ios/Text/RNSVGTextPath.h b/ios/Text/RNSVGTextPath.h index 59ffc5b97..71125673e 100644 --- a/ios/Text/RNSVGTextPath.h +++ b/ios/Text/RNSVGTextPath.h @@ -14,8 +14,13 @@ @interface RNSVGTextPath : RNSVGText @property (nonatomic, strong) NSString *href; +@property (nonatomic, strong) NSString *side; +@property (nonatomic, strong) NSString *method; +@property (nonatomic, strong) NSString *midLine; +@property (nonatomic, strong) NSString *spacing; @property (nonatomic, strong) NSString *startOffset; +- (RNSVGPath *)getPath; - (RNSVGBezierTransformer *)getBezierTransformer; @end diff --git a/ios/Text/RNSVGTextPath.m b/ios/Text/RNSVGTextPath.m index d5bea26f5..3f28dcfc8 100644 --- a/ios/Text/RNSVGTextPath.m +++ b/ios/Text/RNSVGTextPath.m @@ -17,7 +17,7 @@ - (void)renderLayerTo:(CGContextRef)context [self renderGroupTo:context]; } -- (RNSVGBezierTransformer *)getBezierTransformer +- (RNSVGPath *)getPath { RNSVGSvgView *svg = [self getSvgView]; RNSVGNode *template = [svg getDefinedTemplate:self.href]; @@ -28,6 +28,12 @@ - (RNSVGBezierTransformer *)getBezierTransformer } RNSVGPath *path = (RNSVGPath *)template; + return path; +} + +- (RNSVGBezierTransformer *)getBezierTransformer +{ + RNSVGPath *path = [self getPath]; CGFloat startOffset = [self relativeOnWidth:self.startOffset]; return [[RNSVGBezierTransformer alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves] startOffset:startOffset]; diff --git a/ios/Text/TextAnchor.h b/ios/Text/TextAnchor.h new file mode 100644 index 000000000..a252a4a93 --- /dev/null +++ b/ios/Text/TextAnchor.h @@ -0,0 +1,19 @@ +#import + +#if !defined (TextAnchor_) +#define TextAnchor_ + +NS_ENUM(NSInteger, TextAnchor) { + TextAnchorStart, + TextAnchorMiddle, + TextAnchorEnd, + TextAnchorDEFAULT = TextAnchorStart, +}; + +static NSString* const TextAnchorStrings[] = {@"start", @"middle", @"end", nil}; + +NSString* TextAnchorToString( enum TextAnchor fw ); + +enum TextAnchor TextAnchorFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextAnchor.m b/ios/Text/TextAnchor.m new file mode 100644 index 000000000..2d97c56f4 --- /dev/null +++ b/ios/Text/TextAnchor.m @@ -0,0 +1,18 @@ +#import "TextAnchor.h" + +NSString* TextAnchorToString( enum TextAnchor fw ) +{ + return TextAnchorStrings[fw]; +} + +enum TextAnchor TextAnchorFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextAnchorStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextAnchorDEFAULT; +} diff --git a/ios/Text/TextDecoration.h b/ios/Text/TextDecoration.h new file mode 100644 index 000000000..2e49bf0d5 --- /dev/null +++ b/ios/Text/TextDecoration.h @@ -0,0 +1,21 @@ +#import + +#if !defined (TextDecoration_) +#define TextDecoration_ + +NS_ENUM(NSInteger, TextDecoration) { + TextDecorationNone, + TextDecorationUnderline, + TextDecorationOverline, + TextDecorationLineThrough, + TextDecorationBlink, + TextDecorationDEFAULT = TextDecorationNone, +}; + +static NSString* const TextDecorationStrings[] = {@"None", @"Underline", @"Overline", @"LineThrough", @"Blink", nil}; + +NSString* TextDecorationToString( enum TextDecoration fw ); + +enum TextDecoration TextDecorationFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextDecoration.m b/ios/Text/TextDecoration.m new file mode 100644 index 000000000..67e22536d --- /dev/null +++ b/ios/Text/TextDecoration.m @@ -0,0 +1,18 @@ +#import "TextDecoration.h" + +NSString* TextDecorationToString( enum TextDecoration fw ) +{ + return TextDecorationStrings[fw]; +} + +enum TextDecoration TextDecorationFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextDecorationStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextDecorationDEFAULT; +} diff --git a/ios/Text/TextLengthAdjust.h b/ios/Text/TextLengthAdjust.h new file mode 100644 index 000000000..6e3c386eb --- /dev/null +++ b/ios/Text/TextLengthAdjust.h @@ -0,0 +1,18 @@ +#import + +#if !defined (TextLengthAdjust_) +#define TextLengthAdjust_ + +NS_ENUM(NSInteger, TextLengthAdjust) { + TextLengthAdjustSpacing, + TextLengthAdjustSpacingAndGlyphs, + TextLengthAdjustDEFAULT = TextLengthAdjustSpacing, +}; + +static NSString* const TextLengthAdjustStrings[] = {@"spacing", @"spacingAndGlyphs", nil}; + +NSString* TextLengthAdjustToString( enum TextLengthAdjust fw ); + +enum TextLengthAdjust TextLengthAdjustFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextLengthAdjust.m b/ios/Text/TextLengthAdjust.m new file mode 100644 index 000000000..3b29e9668 --- /dev/null +++ b/ios/Text/TextLengthAdjust.m @@ -0,0 +1,18 @@ +#import "TextLengthAdjust.h" + +NSString* TextLengthAdjustToString( enum TextLengthAdjust fw ) +{ + return TextLengthAdjustStrings[fw]; +} + +enum TextLengthAdjust TextLengthAdjustFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextLengthAdjustStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextLengthAdjustDEFAULT; +} diff --git a/ios/Text/TextPathMethod.h b/ios/Text/TextPathMethod.h new file mode 100644 index 000000000..5561f0058 --- /dev/null +++ b/ios/Text/TextPathMethod.h @@ -0,0 +1,18 @@ +#import + +#if !defined (TextPathMethod_) +#define TextPathMethod_ + +NS_ENUM(NSInteger, TextPathMethod) { + TextPathMethodAlign, + TextPathMethodStretch, + TextPathMethodDEFAULT = TextPathMethodAlign, +}; + +static NSString* const TextPathMethodStrings[] = {@"align", @"stretch", nil}; + +NSString* TextPathMethodToString( enum TextPathMethod fw ); + +enum TextPathMethod TextPathMethodFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextPathMethod.m b/ios/Text/TextPathMethod.m new file mode 100644 index 000000000..4a218815f --- /dev/null +++ b/ios/Text/TextPathMethod.m @@ -0,0 +1,18 @@ +#import "TextPathMethod.h" + +NSString* TextPathMethodToString( enum TextPathMethod fw ) +{ + return TextPathMethodStrings[fw]; +} + +enum TextPathMethod TextPathMethodFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextPathMethodStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextPathMethodDEFAULT; +} diff --git a/ios/Text/TextPathMidLine.h b/ios/Text/TextPathMidLine.h new file mode 100644 index 000000000..ab5d7c9c0 --- /dev/null +++ b/ios/Text/TextPathMidLine.h @@ -0,0 +1,18 @@ +#import + +#if !defined (TextPathMidLine_) +#define TextPathMidLine_ + +NS_ENUM(NSInteger, TextPathMidLine) { + TextPathMidLineSharp, + TextPathMidLineSmooth, + TextPathMidLineDEFAULT = TextPathMidLineSharp, +}; + +static NSString* const TextPathMidLineStrings[] = {@"sharp", @"smooth", nil}; + +NSString* TextPathMidLineToString( enum TextPathMidLine fw ); + +enum TextPathMidLine TextPathMidLineFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextPathMidLine.m b/ios/Text/TextPathMidLine.m new file mode 100644 index 000000000..13e26ace6 --- /dev/null +++ b/ios/Text/TextPathMidLine.m @@ -0,0 +1,18 @@ +#import "TextPathMidLine.h" + +NSString* TextPathMidLineToString( enum TextPathMidLine fw ) +{ + return TextPathMidLineStrings[fw]; +} + +enum TextPathMidLine TextPathMidLineFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextPathMidLineStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextPathMidLineDEFAULT; +} diff --git a/ios/Text/TextPathSide.h b/ios/Text/TextPathSide.h new file mode 100644 index 000000000..281d13857 --- /dev/null +++ b/ios/Text/TextPathSide.h @@ -0,0 +1,18 @@ +#import + +#if !defined (TextPathSide_) +#define TextPathSide_ + +NS_ENUM(NSInteger, TextPathSide) { + TextPathSideLeft, + TextPathSideRight, + TextPathSideDEFAULT = TextPathSideLeft, +}; + +static NSString* const TextPathSideStrings[] = {@"left", @"right", nil}; + +NSString* TextPathSideToString( enum TextPathSide fw ); + +enum TextPathSide TextPathSideFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextPathSide.m b/ios/Text/TextPathSide.m new file mode 100644 index 000000000..2db7a3e73 --- /dev/null +++ b/ios/Text/TextPathSide.m @@ -0,0 +1,18 @@ +#import "TextPathSide.h" + +NSString* TextPathSideToString( enum TextPathSide fw ) +{ + return TextPathSideStrings[fw]; +} + +enum TextPathSide TextPathSideFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextPathSideStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextPathSideDEFAULT; +} diff --git a/ios/Text/TextPathSpacing.h b/ios/Text/TextPathSpacing.h new file mode 100644 index 000000000..30c7e5a5f --- /dev/null +++ b/ios/Text/TextPathSpacing.h @@ -0,0 +1,18 @@ +#import + +#if !defined (TextPathSpacing_) +#define TextPathSpacing_ + +NS_ENUM(NSInteger, TextPathSpacing) { + TextPathSpacingAutoSpacing, + TextPathSpacingExact, + TextPathSpacingDEFAULT = TextPathSpacingAutoSpacing, +}; + +static NSString* const TextPathSpacingStrings[] = {@"auto", @"exact", nil}; + +NSString* TextPathSpacingToString( enum TextPathSpacing fw ); + +enum TextPathSpacing TextPathSpacingFromString( NSString* s ); + +#endif diff --git a/ios/Text/TextPathSpacing.m b/ios/Text/TextPathSpacing.m new file mode 100644 index 000000000..30c7e5a5f --- /dev/null +++ b/ios/Text/TextPathSpacing.m @@ -0,0 +1,18 @@ +#import + +#if !defined (TextPathSpacing_) +#define TextPathSpacing_ + +NS_ENUM(NSInteger, TextPathSpacing) { + TextPathSpacingAutoSpacing, + TextPathSpacingExact, + TextPathSpacingDEFAULT = TextPathSpacingAutoSpacing, +}; + +static NSString* const TextPathSpacingStrings[] = {@"auto", @"exact", nil}; + +NSString* TextPathSpacingToString( enum TextPathSpacing fw ); + +enum TextPathSpacing TextPathSpacingFromString( NSString* s ); + +#endif diff --git a/ios/Utils/UIBezierPath+getTransformAtDistance.h b/ios/Utils/UIBezierPath+getTransformAtDistance.h new file mode 100644 index 000000000..40161ee64 --- /dev/null +++ b/ios/Utils/UIBezierPath+getTransformAtDistance.h @@ -0,0 +1,15 @@ +// +// UIBezierPath+getTransformAtDistance.h +// RNSVG +// +// Created by Janne Gylling on 18/08/2017. +// +// + +#import + +@interface UIBezierPath (getTransformAtDistance) + +- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance; + +@end diff --git a/ios/Utils/UIBezierPath+getTransformAtDistance.m b/ios/Utils/UIBezierPath+getTransformAtDistance.m new file mode 100644 index 000000000..2a16c0fbe --- /dev/null +++ b/ios/Utils/UIBezierPath+getTransformAtDistance.m @@ -0,0 +1,18 @@ +// +// UIBezierPath+getTransformAtDistance.m +// RNSVG +// +// Created by Janne Gylling on 18/08/2017. +// +// + +#import "UIBezierPath+getTransformAtDistance.h" + +@implementation UIBezierPath (getTransformAtDistance) + +- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance +{ + return CGAffineTransformIdentity; +} + +@end From 13d5157fc1684df16db9d67bbd514a3abc32cf66 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 23 Aug 2017 16:22:49 +0300 Subject: [PATCH 177/198] Cleanup. --- ios/Elements/RNSVGGroup.m | 4 ++-- ios/Text/RNSVGTSpan.m | 16 +++++++--------- ios/Text/RNSVGText.m | 4 +++- ios/Text/TextPathSpacing.m | 32 ++++++++++++++++---------------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 8e4d5540e..0515048b9 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -70,13 +70,13 @@ - (GlyphContext *)getGlyphContext - (void)pushGlyphContext { - [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font]; + //[[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font]; [[[self getTextRoot] getGlyphContext] pushContextWithRNSVGGroup:self font:self.font]; } - (void)popGlyphContext { - [[[self getTextRoot] getRNSVGGlyphContext] popContext]; + //[[[self getTextRoot] getRNSVGGlyphContext] popContext]; [[[self getTextRoot] getGlyphContext] popContext]; } diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index d5f8edd6f..a7fc3844f 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -110,15 +110,14 @@ - (CGMutablePathRef)getLinePath:(CTLineRef)line text:(NSString *)str font:(CTFon CFArrayRef runs = CTLineGetGlyphRuns(line); GlyphContext* gc = [[self getTextRoot] getGlyphContext]; FontData* font = [gc getFont]; - NSUInteger length = str.length; - NSUInteger n = length; + NSUInteger n = str.length; + CGSize glyph_advances[n]; unichar characters[n]; - CFStringGetCharacters((__bridge CFStringRef) str, CFRangeMake(0, n), characters); CGGlyph glyphs[n]; - CGSize glyph_advances[n]; + CFStringGetCharacters((__bridge CFStringRef) str, CFRangeMake(0, n), characters); CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, n); - CTFontGetAdvancesForGlyphs(fontRef, kCTFontDefaultOrientation, glyphs, glyph_advances, n); + CTFontGetAdvancesForGlyphs(fontRef, kCTFontOrientationHorizontal, glyphs, glyph_advances, n); /* * * Three properties affect the space between characters and words: @@ -473,7 +472,7 @@ A negative value is an error (see Error processing). switch (mLengthAdjust) { default: case TextLengthAdjustSpacing: - letterSpacing += (author - textMeasure) / (length - 1); + letterSpacing += (author - textMeasure) / (n - 1); break; case TextLengthAdjustSpacingAndGlyphs: scaleSpacingAndGlyphs = author / textMeasure; @@ -805,11 +804,11 @@ - (void)setupTextPath:(CGContextRef)context _path = nil; textPath = nil; textPathPath = nil; - __block RNSVGBezierTransformer *bezierTransformer; + //_bezierTransformer = nil; [self traverseTextSuperviews:^(__kindof RNSVGText *node) { if ([node class] == [RNSVGTextPath class]) { textPath = (RNSVGTextPath*) node; - bezierTransformer = [textPath getBezierTransformer]; + //_bezierTransformer = [textPath getBezierTransformer]; textPathPath = [textPath getPath]; _path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; pathLength = [_path length]; @@ -817,7 +816,6 @@ - (void)setupTextPath:(CGContextRef)context } return YES; }]; - _bezierTransformer = bezierTransformer; } - (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index cf94e2408..328e617b7 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -107,11 +107,12 @@ - (GlyphContext *)getGlyphContext - (void)pushGlyphContext { + /* [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font deltaX:self.deltaX deltaY:self.deltaY positionX:self.positionX - positionY:self.positionY]; + positionY:self.positionY];*/ [[[self getTextRoot] getGlyphContext] pushContextwithRNSVGText:self reset:false font:self.font @@ -124,6 +125,7 @@ - (void)pushGlyphContext - (void)popGlyphContext { + //[[[self getTextRoot] getRNSVGGlyphContext] popContext]; [[[self getTextRoot] getGlyphContext] popContext]; } diff --git a/ios/Text/TextPathSpacing.m b/ios/Text/TextPathSpacing.m index 30c7e5a5f..b62139ee5 100644 --- a/ios/Text/TextPathSpacing.m +++ b/ios/Text/TextPathSpacing.m @@ -1,18 +1,18 @@ -#import +#import "TextPathSpacing.h" -#if !defined (TextPathSpacing_) -#define TextPathSpacing_ +NSString* TextPathSpacingToString( enum TextPathSpacing fw ) +{ + return TextPathSpacingStrings[fw]; +} -NS_ENUM(NSInteger, TextPathSpacing) { - TextPathSpacingAutoSpacing, - TextPathSpacingExact, - TextPathSpacingDEFAULT = TextPathSpacingAutoSpacing, -}; - -static NSString* const TextPathSpacingStrings[] = {@"auto", @"exact", nil}; - -NSString* TextPathSpacingToString( enum TextPathSpacing fw ); - -enum TextPathSpacing TextPathSpacingFromString( NSString* s ); - -#endif +enum TextPathSpacing TextPathSpacingFromString( NSString* s ) +{ + NSInteger i; + NSString* fw; + for (i = 0; fw = TextPathSpacingStrings[i], fw != nil; i++) { + if ([fw isEqualToString:s]) { + return i; + } + } + return TextPathSpacingDEFAULT; +} From 5372a3078e5d77df33cfcc33bee44c40e2d37691 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 24 Aug 2017 12:42:18 +0300 Subject: [PATCH 178/198] Cleanup headers --- ios/Text/FontStyle.h | 4 ++-- ios/Text/FontVariantLigatures.h | 4 ++-- ios/Text/FontWeight.h | 4 ++-- ios/Text/PropHelper.h | 6 ++++-- ios/Text/TextAnchor.h | 4 ++-- ios/Text/TextDecoration.h | 4 ++-- ios/Text/TextLengthAdjust.h | 4 ++-- ios/Text/TextPathMethod.h | 4 ++-- ios/Text/TextPathMidLine.h | 4 ++-- ios/Text/TextPathSide.h | 4 ++-- ios/Text/TextPathSpacing.h | 4 ++-- 11 files changed, 24 insertions(+), 22 deletions(-) diff --git a/ios/Text/FontStyle.h b/ios/Text/FontStyle.h index c3db08642..9bb37ef90 100644 --- a/ios/Text/FontStyle.h +++ b/ios/Text/FontStyle.h @@ -1,7 +1,7 @@ #import -#if !defined (FontStyle_) -#define FontStyle_ +#ifndef FontStyle_h +#define FontStyle_h NS_ENUM(NSInteger, FontStyle) { FontStyleNormal, diff --git a/ios/Text/FontVariantLigatures.h b/ios/Text/FontVariantLigatures.h index 29494e383..ca9d8d011 100644 --- a/ios/Text/FontVariantLigatures.h +++ b/ios/Text/FontVariantLigatures.h @@ -1,7 +1,7 @@ #import -#if !defined (FontVariantLigatures_) -#define FontVariantLigatures_ +#ifndef FontVariantLigatures_h +#define FontVariantLigatures_h NS_ENUM(NSInteger, FontVariantLigatures) { FontVariantLigaturesNormal, diff --git a/ios/Text/FontWeight.h b/ios/Text/FontWeight.h index 9118343a8..42af8ea07 100644 --- a/ios/Text/FontWeight.h +++ b/ios/Text/FontWeight.h @@ -1,7 +1,7 @@ #import -#if !defined (FontWeight_) -#define FontWeight_ +#ifndef FontWeight_h +#define FontWeight_h NS_ENUM(NSInteger, FontWeight) { FontWeightNormal, diff --git a/ios/Text/PropHelper.h b/ios/Text/PropHelper.h index 37d808a26..1dfd0708e 100644 --- a/ios/Text/PropHelper.h +++ b/ios/Text/PropHelper.h @@ -1,8 +1,9 @@ #import #import -#if !defined (PropHelper_) -#define PropHelper_ +#ifndef PropHelper_h +#define PropHelper_h + @interface PropHelper : NSObject + (double) fromRelativeWithNSString:(NSString *)length @@ -12,4 +13,5 @@ fontSize:(double)fontSize; @end + #endif diff --git a/ios/Text/TextAnchor.h b/ios/Text/TextAnchor.h index a252a4a93..24213679a 100644 --- a/ios/Text/TextAnchor.h +++ b/ios/Text/TextAnchor.h @@ -1,7 +1,7 @@ #import -#if !defined (TextAnchor_) -#define TextAnchor_ +#ifndef TextAnchor_h +#define TextAnchor_h NS_ENUM(NSInteger, TextAnchor) { TextAnchorStart, diff --git a/ios/Text/TextDecoration.h b/ios/Text/TextDecoration.h index 2e49bf0d5..e80e16c08 100644 --- a/ios/Text/TextDecoration.h +++ b/ios/Text/TextDecoration.h @@ -1,7 +1,7 @@ #import -#if !defined (TextDecoration_) -#define TextDecoration_ +#ifndef TextDecoration_h +#define TextDecoration_h NS_ENUM(NSInteger, TextDecoration) { TextDecorationNone, diff --git a/ios/Text/TextLengthAdjust.h b/ios/Text/TextLengthAdjust.h index 6e3c386eb..0f7aeccbb 100644 --- a/ios/Text/TextLengthAdjust.h +++ b/ios/Text/TextLengthAdjust.h @@ -1,7 +1,7 @@ #import -#if !defined (TextLengthAdjust_) -#define TextLengthAdjust_ +#ifndef TextLengthAdjust_h +#define TextLengthAdjust_h NS_ENUM(NSInteger, TextLengthAdjust) { TextLengthAdjustSpacing, diff --git a/ios/Text/TextPathMethod.h b/ios/Text/TextPathMethod.h index 5561f0058..b38177331 100644 --- a/ios/Text/TextPathMethod.h +++ b/ios/Text/TextPathMethod.h @@ -1,7 +1,7 @@ #import -#if !defined (TextPathMethod_) -#define TextPathMethod_ +#ifndef TextPathMethod_h +#define TextPathMethod_h NS_ENUM(NSInteger, TextPathMethod) { TextPathMethodAlign, diff --git a/ios/Text/TextPathMidLine.h b/ios/Text/TextPathMidLine.h index ab5d7c9c0..cc36c0b76 100644 --- a/ios/Text/TextPathMidLine.h +++ b/ios/Text/TextPathMidLine.h @@ -1,7 +1,7 @@ #import -#if !defined (TextPathMidLine_) -#define TextPathMidLine_ +#ifndef TextPathMidLine_h +#define TextPathMidLine_h NS_ENUM(NSInteger, TextPathMidLine) { TextPathMidLineSharp, diff --git a/ios/Text/TextPathSide.h b/ios/Text/TextPathSide.h index 281d13857..155259a2e 100644 --- a/ios/Text/TextPathSide.h +++ b/ios/Text/TextPathSide.h @@ -1,7 +1,7 @@ #import -#if !defined (TextPathSide_) -#define TextPathSide_ +#ifndef TextPathSide_h +#define TextPathSide_h NS_ENUM(NSInteger, TextPathSide) { TextPathSideLeft, diff --git a/ios/Text/TextPathSpacing.h b/ios/Text/TextPathSpacing.h index 30c7e5a5f..18d6b6242 100644 --- a/ios/Text/TextPathSpacing.h +++ b/ios/Text/TextPathSpacing.h @@ -1,7 +1,7 @@ #import -#if !defined (TextPathSpacing_) -#define TextPathSpacing_ +#ifndef TextPathSpacing_h +#define TextPathSpacing_h NS_ENUM(NSInteger, TextPathSpacing) { TextPathSpacingAutoSpacing, From e604d7fb8f833bb8ee5dc94cdd57acd97a4eb55d Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 24 Aug 2017 19:14:11 +0300 Subject: [PATCH 179/198] Simplify enum parsing --- ios/Text/AlignmentBaseline.m | 7 +++---- ios/Text/FontStyle.m | 7 +++---- ios/Text/FontVariantLigatures.m | 7 +++---- ios/Text/FontWeight.m | 7 +++---- ios/Text/TextAnchor.m | 7 +++---- ios/Text/TextDecoration.m | 7 +++---- ios/Text/TextLengthAdjust.m | 7 +++---- ios/Text/TextPathMethod.m | 7 +++---- ios/Text/TextPathMidLine.m | 7 +++---- ios/Text/TextPathSide.m | 7 +++---- ios/Text/TextPathSpacing.m | 7 +++---- 11 files changed, 33 insertions(+), 44 deletions(-) diff --git a/ios/Text/AlignmentBaseline.m b/ios/Text/AlignmentBaseline.m index b28d6f251..b1985c63f 100644 --- a/ios/Text/AlignmentBaseline.m +++ b/ios/Text/AlignmentBaseline.m @@ -7,10 +7,9 @@ enum AlignmentBaseline AlignmentBaselineFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = AlignmentBaselineStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(AlignmentBaselineStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:AlignmentBaselineStrings[i]]) { return i; } } diff --git a/ios/Text/FontStyle.m b/ios/Text/FontStyle.m index 8c61d9e0e..a1c0a2b73 100644 --- a/ios/Text/FontStyle.m +++ b/ios/Text/FontStyle.m @@ -7,10 +7,9 @@ enum FontStyle FontStyleFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = FontStyleStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(FontStyleStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:FontStyleStrings[i]]) { return i; } } diff --git a/ios/Text/FontVariantLigatures.m b/ios/Text/FontVariantLigatures.m index 3d182af5a..73c0ac8b8 100644 --- a/ios/Text/FontVariantLigatures.m +++ b/ios/Text/FontVariantLigatures.m @@ -7,10 +7,9 @@ enum FontVariantLigatures FontVariantLigaturesFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = FontVariantLigaturesStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(FontVariantLigaturesStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:FontVariantLigaturesStrings[i]]) { return i; } } diff --git a/ios/Text/FontWeight.m b/ios/Text/FontWeight.m index 230f1dc25..3a1e6d76d 100644 --- a/ios/Text/FontWeight.m +++ b/ios/Text/FontWeight.m @@ -7,10 +7,9 @@ enum FontWeight FontWeightFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = FontWeightStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(FontWeightStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:FontWeightStrings[i]]) { return i; } } diff --git a/ios/Text/TextAnchor.m b/ios/Text/TextAnchor.m index 2d97c56f4..f295551c7 100644 --- a/ios/Text/TextAnchor.m +++ b/ios/Text/TextAnchor.m @@ -7,10 +7,9 @@ enum TextAnchor TextAnchorFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextAnchorStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextAnchorStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextAnchorStrings[i]]) { return i; } } diff --git a/ios/Text/TextDecoration.m b/ios/Text/TextDecoration.m index 67e22536d..5bf31361f 100644 --- a/ios/Text/TextDecoration.m +++ b/ios/Text/TextDecoration.m @@ -7,10 +7,9 @@ enum TextDecoration TextDecorationFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextDecorationStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextDecorationStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextDecorationStrings[i]]) { return i; } } diff --git a/ios/Text/TextLengthAdjust.m b/ios/Text/TextLengthAdjust.m index 3b29e9668..0ad442417 100644 --- a/ios/Text/TextLengthAdjust.m +++ b/ios/Text/TextLengthAdjust.m @@ -7,10 +7,9 @@ enum TextLengthAdjust TextLengthAdjustFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextLengthAdjustStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextLengthAdjustStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextLengthAdjustStrings[i]]) { return i; } } diff --git a/ios/Text/TextPathMethod.m b/ios/Text/TextPathMethod.m index 4a218815f..23aef0136 100644 --- a/ios/Text/TextPathMethod.m +++ b/ios/Text/TextPathMethod.m @@ -7,10 +7,9 @@ enum TextPathMethod TextPathMethodFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextPathMethodStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextPathMethodStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextPathMethodStrings[i]]) { return i; } } diff --git a/ios/Text/TextPathMidLine.m b/ios/Text/TextPathMidLine.m index 13e26ace6..6e4d3e435 100644 --- a/ios/Text/TextPathMidLine.m +++ b/ios/Text/TextPathMidLine.m @@ -7,10 +7,9 @@ enum TextPathMidLine TextPathMidLineFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextPathMidLineStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextPathMidLineStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextPathMidLineStrings[i]]) { return i; } } diff --git a/ios/Text/TextPathSide.m b/ios/Text/TextPathSide.m index 2db7a3e73..d0bcb8209 100644 --- a/ios/Text/TextPathSide.m +++ b/ios/Text/TextPathSide.m @@ -7,10 +7,9 @@ enum TextPathSide TextPathSideFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextPathSideStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextPathSideStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextPathSideStrings[i]]) { return i; } } diff --git a/ios/Text/TextPathSpacing.m b/ios/Text/TextPathSpacing.m index b62139ee5..08735d76f 100644 --- a/ios/Text/TextPathSpacing.m +++ b/ios/Text/TextPathSpacing.m @@ -7,10 +7,9 @@ enum TextPathSpacing TextPathSpacingFromString( NSString* s ) { - NSInteger i; - NSString* fw; - for (i = 0; fw = TextPathSpacingStrings[i], fw != nil; i++) { - if ([fw isEqualToString:s]) { + const NSUInteger l = sizeof(TextPathSpacingStrings) / sizeof(NSString*); + for (NSUInteger i = 0; i < l; i++) { + if ([s isEqualToString:TextPathSpacingStrings[i]]) { return i; } } From 95736f2f6967ad7fd9e9b4202c4a84e50e7fe8cc Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 24 Aug 2017 21:43:34 +0300 Subject: [PATCH 180/198] Disable ligatures on ios (doesn't account for per character advance width correctly yet) --- ios/Text/RNSVGTSpan.m | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index a7fc3844f..bafdc56b5 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -77,11 +77,13 @@ - (CGPathRef)getPath:(CGContextRef)context if (font != nil) { attributes = (__bridge CFDictionaryRef)@{ (NSString *)kCTFontAttributeName: (__bridge id)font, - (NSString *)kCTForegroundColorFromContextAttributeName: @YES + (NSString *)kCTForegroundColorFromContextAttributeName: @YES, + (NSString *)NSLigatureAttributeName: @0 }; } else { attributes = (__bridge CFDictionaryRef)@{ - (NSString *)kCTForegroundColorFromContextAttributeName: @YES + (NSString *)kCTForegroundColorFromContextAttributeName: @YES, + (NSString *)NSLigatureAttributeName: @0 }; } @@ -681,8 +683,8 @@ A negative value is an error (see Error processing). } CFIndex runEnd = CFArrayGetCount(runs); - for (CFIndex i = 0; i < runEnd; i++) { - CTRunRef run = CFArrayGetValueAtIndex(CTLineGetGlyphRuns(line), i); + for (CFIndex r = 0; r < runEnd; r++) { + CTRunRef run = CFArrayGetValueAtIndex(runs, r); CFIndex runGlyphCount = CTRunGetGlyphCount(run); CGPoint positions[runGlyphCount]; @@ -693,14 +695,14 @@ A negative value is an error (see Error processing). CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); - for(CFIndex i = 0; i < runGlyphCount; i++) { - CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); + for(CFIndex g = 0; g < runGlyphCount; g++) { + CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[g], nil); /* Determine the glyph's charwidth (i.e., the amount which the current text position advances horizontally when the glyph is drawn using horizontal text layout). */ - CGFloat charWidth = glyph_advances[i].width * scaleSpacingAndGlyphs; + CGFloat charWidth = glyph_advances[g].width * scaleSpacingAndGlyphs; //CGPoint glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))]; /* From c6028e825da663a377e9f2288cd0f45517baf2e0 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 25 Aug 2017 13:46:03 +0300 Subject: [PATCH 181/198] Fix rotate and G element font settings. --- ios/Text/GlyphContext.m | 129 +++++++++++----------- ios/Text/RNSVGTSpan.m | 70 +++++------- ios/ViewManagers/RNSVGRenderableManager.m | 1 + ios/ViewManagers/RNSVGTextManager.m | 1 + 4 files changed, 97 insertions(+), 104 deletions(-) diff --git a/ios/Text/GlyphContext.m b/ios/Text/GlyphContext.m index 510bda630..e8c0102e7 100644 --- a/ios/Text/GlyphContext.m +++ b/ios/Text/GlyphContext.m @@ -39,17 +39,17 @@ @interface GlyphContext () { NSArray *mDXs_; NSArray *mDYs_; NSArray *mRs_; - int mXsIndex_; - int mYsIndex_; - int mDXsIndex_; - int mDYsIndex_; - int mRsIndex_; - int mXIndex_; - int mYIndex_; - int mDXIndex_; - int mDYIndex_; - int mRIndex_; - int mTop_; + long mXsIndex_; + long mYsIndex_; + long mDXsIndex_; + long mDYsIndex_; + long mRsIndex_; + long mXIndex_; + long mYIndex_; + long mDXIndex_; + long mDYIndex_; + long mRIndex_; + long mTop_; float mScale_; float mWidth_; float mHeight_; @@ -65,7 +65,7 @@ - (void)pushNodeAndFontWithRNSVGGroup:(RNSVGGroup *)node withNSDictionary:(NSDictionary *)font; + (void)incrementIndicesWithNSArray:(NSArray *)indices - withInt:(int)topIndex; + withLong:(long)topIndex; - (void)pushContextwithRNSVGText:(RNSVGText *)node reset:(bool)reset @@ -89,10 +89,10 @@ - (CTFontRef)getGlyphFont NSNumber * fontSize = [NSNumber numberWithDouble:topFont_->fontSize]; NSString * fontWeight = topFont_->fontWeight_; NSString * fontStyle = topFont_->fontStyle_; - + BOOL fontFamilyFound = NO; NSArray *supportedFontFamilyNames = [UIFont familyNames]; - + if ([supportedFontFamilyNames containsObject:fontFamily]) { fontFamilyFound = YES; } else { @@ -104,7 +104,7 @@ - (CTFontRef)getGlyphFont } } fontFamily = fontFamilyFound ? fontFamily : nil; - + return (__bridge CTFontRef)[RCTFont updateFont:nil withFamily:fontFamily size:fontSize @@ -163,36 +163,36 @@ - (void)pushContextwithRNSVGText:(RNSVGText*)node if (x != nil && [x count] != 0) { mXsIndex_++; mXIndex_ = -1; - [mXIndices_ addObject:[NSNumber numberWithInteger:mXIndex_]]; + [mXIndices_ addObject:[NSNumber numberWithLong:mXIndex_]]; mXs_ = x; [mXsContext_ addObject:mXs_]; } if (y != nil && [y count] != 0) { mYsIndex_++; mYIndex_ = -1; - [mYIndices_ addObject:[NSNumber numberWithInteger:mYIndex_]]; + [mYIndices_ addObject:[NSNumber numberWithLong:mYIndex_]]; mYs_ = y; [mYsContext_ addObject:mYs_]; } if (deltaX != nil && [deltaX count] != 0) { mDXsIndex_++; mDXIndex_ = -1; - [mDXIndices_ addObject:[NSNumber numberWithInteger:mDXIndex_]]; + [mDXIndices_ addObject:[NSNumber numberWithLong:mDXIndex_]]; mDXs_ = deltaX; [mDXsContext_ addObject:mDXs_]; } if (deltaY != nil && [deltaY count] != 0) { mDYsIndex_++; mDYIndex_ = -1; - [mDYIndices_ addObject:[NSNumber numberWithInteger:mDYIndex_]]; + [mDYIndices_ addObject:[NSNumber numberWithLong:mDYIndex_]]; mDYs_ = deltaY; [mDYsContext_ addObject:mDYs_]; } if (rotate != nil && [rotate count] != 0) { mRsIndex_++; mRIndex_ = -1; - [mRIndices_ addObject:[NSNumber numberWithInteger:mRIndex_]]; - mRs_ = rotate; + [mRIndices_ addObject:[NSNumber numberWithLong:mRIndex_]]; + mRs_ = [rotate valueForKeyPath:@"self.doubleValue"]; [mRsContext_ addObject:mRs_]; } GlyphContext_pushIndices(self); @@ -206,47 +206,47 @@ - (void)popContext { [mDYsIndices_ removeLastObject]; [mRsIndices_ removeLastObject]; mTop_--; - int x = mXsIndex_; - int y = mYsIndex_; - int dx = mDXsIndex_; - int dy = mDYsIndex_; - int r = mRsIndex_; + long x = mXsIndex_; + long y = mYsIndex_; + long dx = mDXsIndex_; + long dy = mDYsIndex_; + long r = mRsIndex_; topFont_ = [mFontContext_ lastObject]; - mXsIndex_ = [[mXsIndices_ lastObject] intValue]; - mYsIndex_ = [[mYsIndices_ lastObject] intValue]; - mDXsIndex_ = [[mDXsIndices_ lastObject] intValue]; - mDYsIndex_ = [[mDYsIndices_ lastObject] intValue]; - mRsIndex_ = [[mRsIndices_ lastObject] intValue]; + mXsIndex_ = [[mXsIndices_ lastObject] longValue]; + mYsIndex_ = [[mYsIndices_ lastObject] longValue]; + mDXsIndex_ = [[mDXsIndices_ lastObject] longValue]; + mDYsIndex_ = [[mDYsIndices_ lastObject] longValue]; + mRsIndex_ = [[mRsIndices_ lastObject] longValue]; if (x != mXsIndex_) { [mXsContext_ removeObjectAtIndex:x]; mXs_ = [mXsContext_ objectAtIndex:mXsIndex_]; - mXIndex_ = [[mXIndices_ objectAtIndex:mXsIndex_] intValue]; + mXIndex_ = [[mXIndices_ objectAtIndex:mXsIndex_] longValue]; } if (y != mYsIndex_) { [mYsContext_ removeObjectAtIndex:y]; mYs_ = [mYsContext_ objectAtIndex:mYsIndex_]; - mYIndex_ = [[mYIndices_ objectAtIndex:mYsIndex_] intValue]; + mYIndex_ = [[mYIndices_ objectAtIndex:mYsIndex_] longValue]; } if (dx != mDXsIndex_) { [mDXsContext_ removeObjectAtIndex:dx]; mDXs_ = [mDXsContext_ objectAtIndex:mDXsIndex_]; - mDXIndex_ = [[mDXIndices_ objectAtIndex:mDXsIndex_] intValue]; + mDXIndex_ = [[mDXIndices_ objectAtIndex:mDXsIndex_] longValue]; } if (dy != mDYsIndex_) { [mDYsContext_ removeObjectAtIndex:dy]; mDYs_ = [mDYsContext_ objectAtIndex:mDYsIndex_]; - mDYIndex_ = [[mDYIndices_ objectAtIndex:mDYsIndex_] intValue]; + mDYIndex_ = [[mDYIndices_ objectAtIndex:mDYsIndex_] longValue]; } if (r != mRsIndex_) { [mRsContext_ removeObjectAtIndex:r]; mRs_ = [mRsContext_ objectAtIndex:mRsIndex_]; - mRIndex_ = [[mRIndices_ objectAtIndex:mRsIndex_] intValue]; + mRIndex_ = [[mRIndices_ objectAtIndex:mRsIndex_] longValue]; } } + (void)incrementIndicesWithNSArray:(NSMutableArray *)indices - withInt:(int)topIndex { - GlyphContext_incrementIndicesWithNSArray_withInt_(indices, topIndex); + withLong:(long)topIndex { + GlyphContext_incrementIndicesWithNSArray_withLong_(indices, topIndex); } - (double)getFontSize { @@ -254,8 +254,8 @@ - (double)getFontSize { } - (double)nextXWithDouble:(double)advance { - GlyphContext_incrementIndicesWithNSArray_withInt_(mXIndices_, mXsIndex_); - int nextIndex = mXIndex_ + 1; + GlyphContext_incrementIndicesWithNSArray_withLong_(mXIndices_, mXsIndex_); + long nextIndex = mXIndex_ + 1; if (nextIndex < [mXs_ count]) { mDX_ = 0; mXIndex_ = nextIndex; @@ -271,8 +271,8 @@ - (double)nextXWithDouble:(double)advance { } - (double)nextY { - GlyphContext_incrementIndicesWithNSArray_withInt_(mYIndices_, mYsIndex_); - int nextIndex = mYIndex_ + 1; + GlyphContext_incrementIndicesWithNSArray_withLong_(mYIndices_, mYsIndex_); + long nextIndex = mYIndex_ + 1; if (nextIndex < [mYs_ count]) { mDY_ = 0; mYIndex_ = nextIndex; @@ -287,8 +287,8 @@ - (double)nextY { } - (double)nextDeltaX { - GlyphContext_incrementIndicesWithNSArray_withInt_(mDXIndices_, mDXsIndex_); - int nextIndex = mDXIndex_ + 1; + GlyphContext_incrementIndicesWithNSArray_withLong_(mDXIndices_, mDXsIndex_); + long nextIndex = mDXIndex_ + 1; if (nextIndex < [mDXs_ count]) { mDXIndex_ = nextIndex; NSString *string = [mDXs_ objectAtIndex:nextIndex]; @@ -303,8 +303,8 @@ - (double)nextDeltaX { } - (double)nextDeltaY { - GlyphContext_incrementIndicesWithNSArray_withInt_(mDYIndices_, mDYsIndex_); - int nextIndex = mDYIndex_ + 1; + GlyphContext_incrementIndicesWithNSArray_withLong_(mDYIndices_, mDYsIndex_); + long nextIndex = mDYIndex_ + 1; if (nextIndex < [mDYs_ count]) { mDYIndex_ = nextIndex; NSString *string = [mDYs_ objectAtIndex:nextIndex]; @@ -319,10 +319,13 @@ - (double)nextDeltaY { } - (NSNumber*)nextRotation { - GlyphContext_incrementIndicesWithNSArray_withInt_(mRIndices_, mRsIndex_); - int nextIndex = mRIndex_ + 1; - if (nextIndex < [mRs_ count]) { + GlyphContext_incrementIndicesWithNSArray_withLong_(mRIndices_, mRsIndex_); + long nextIndex = mRIndex_ + 1; + long count = [mRs_ count]; + if (nextIndex < count) { mRIndex_ = nextIndex; + } else { + mRIndex_ = count - 1; } return mRs_[mRIndex_]; } @@ -336,11 +339,11 @@ - (float)getHeight { } void GlyphContext_pushIndices(GlyphContext *self) { - [self->mXsIndices_ addObject:[NSNumber numberWithInteger:self->mXsIndex_]]; - [self->mYsIndices_ addObject:[NSNumber numberWithInteger:self->mYsIndex_]]; - [self->mDXsIndices_ addObject:[NSNumber numberWithInteger:self->mDXsIndex_]]; - [self->mDYsIndices_ addObject:[NSNumber numberWithInteger:self->mDYsIndex_]]; - [self->mRsIndices_ addObject:[NSNumber numberWithInteger:self->mRsIndex_]]; + [self->mXsIndices_ addObject:[NSNumber numberWithLong:self->mXsIndex_]]; + [self->mYsIndices_ addObject:[NSNumber numberWithLong:self->mYsIndex_]]; + [self->mDXsIndices_ addObject:[NSNumber numberWithLong:self->mDXsIndex_]]; + [self->mDYsIndices_ addObject:[NSNumber numberWithLong:self->mDYsIndex_]]; + [self->mRsIndices_ addObject:[NSNumber numberWithLong:self->mRsIndex_]]; } void GlyphContext_initWithFloat_withFloat_withFloat_(GlyphContext *self, float scale_, float width, float height) { @@ -380,11 +383,11 @@ void GlyphContext_initWithFloat_withFloat_withFloat_(GlyphContext *self, float s [self->mDXsContext_ addObject:self->mDXs_]; [self->mDYsContext_ addObject:self->mDYs_]; [self->mRsContext_ addObject:self->mRs_]; - [self->mXIndices_ addObject:[NSNumber numberWithInteger:self->mXIndex_]]; - [self->mYIndices_ addObject:[NSNumber numberWithInteger:self->mYIndex_]]; - [self->mDXIndices_ addObject:[NSNumber numberWithInteger:self->mDXIndex_]]; - [self->mDYIndices_ addObject:[NSNumber numberWithInteger:self->mDYIndex_]]; - [self->mRIndices_ addObject:[NSNumber numberWithInteger:self->mRIndex_]]; + [self->mXIndices_ addObject:[NSNumber numberWithLong:self->mXIndex_]]; + [self->mYIndices_ addObject:[NSNumber numberWithLong:self->mYIndex_]]; + [self->mDXIndices_ addObject:[NSNumber numberWithLong:self->mDXIndex_]]; + [self->mDYIndices_ addObject:[NSNumber numberWithLong:self->mDYIndex_]]; + [self->mRIndices_ addObject:[NSNumber numberWithLong:self->mRIndex_]]; [self->mFontContext_ addObject:self->topFont_]; GlyphContext_pushIndices(self); } @@ -428,10 +431,10 @@ void GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(GlyphContext * self->topFont_ = data; } -void GlyphContext_incrementIndicesWithNSArray_withInt_(NSMutableArray *indices, int topIndex) { - for (int index = topIndex; index >= 0; index--) { - int xIndex = [[indices objectAtIndex:index] intValue]; - [indices setObject:[NSNumber numberWithInteger:xIndex + 1] atIndexedSubscript:index]; +void GlyphContext_incrementIndicesWithNSArray_withLong_(NSMutableArray *indices, long topIndex) { + for (long index = topIndex; index >= 0; index--) { + long xIndex = [[indices objectAtIndex:index] longValue]; + [indices setObject:[NSNumber numberWithLong:xIndex + 1] atIndexedSubscript:index]; } } @end diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index bafdc56b5..048f4c86d 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -67,59 +67,42 @@ - (CGPathRef)getPath:(CGContextRef)context [self setupTextPath:context]; - CGMutablePathRef path = CGPathCreateMutable(); - [self pushGlyphContext]; - CTFontRef font = [self getFontFromContext]; + CGMutablePathRef path = [self getLinePath:text]; + + _cache = CGPathRetain(CFAutorelease(CGPathCreateCopy(path))); + + [self popGlyphContext]; + + return (CGPathRef)CFAutorelease(path); +} + +- (CGMutablePathRef)getLinePath:(NSString *)str +{ // Create a dictionary for this font + CTFontRef fontRef = [self getFontFromContext]; CFDictionaryRef attributes; - if (font != nil) { + if (fontRef != nil) { attributes = (__bridge CFDictionaryRef)@{ - (NSString *)kCTFontAttributeName: (__bridge id)font, + (NSString *)kCTFontAttributeName: (__bridge id)fontRef, (NSString *)kCTForegroundColorFromContextAttributeName: @YES, - (NSString *)NSLigatureAttributeName: @0 - }; + (NSString *)NSLigatureAttributeName: @0 }; } else { attributes = (__bridge CFDictionaryRef)@{ (NSString *)kCTForegroundColorFromContextAttributeName: @YES, - (NSString *)NSLigatureAttributeName: @0 - }; + (NSString *)NSLigatureAttributeName: @0 }; } - CFStringRef string = (__bridge CFStringRef)text; + CFStringRef string = (__bridge CFStringRef)str; CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CTLineRef line = CTLineCreateWithAttributedString(attrString); - CGMutablePathRef linePath = [self getLinePath:line text:text font:font]; - CGAffineTransform offset = CGAffineTransformIdentity; - CGPathAddPath(path, &offset, linePath); - CGPathRelease(linePath); - - _cache = CGPathRetain(CFAutorelease(CGPathCreateCopy(path))); - [self popGlyphContext]; - - // clean up - CFRelease(attrString); - CFRelease(line); - - return (CGPathRef)CFAutorelease(path); -} - -- (CGMutablePathRef)getLinePath:(CTLineRef)line text:(NSString *)str font:(CTFontRef)fontRef -{ CGMutablePathRef path = CGPathCreateMutable(); CFArrayRef runs = CTLineGetGlyphRuns(line); GlyphContext* gc = [[self getTextRoot] getGlyphContext]; FontData* font = [gc getFont]; NSUInteger n = str.length; - CGSize glyph_advances[n]; - unichar characters[n]; - CGGlyph glyphs[n]; - - CFStringGetCharacters((__bridge CFStringRef) str, CFRangeMake(0, n), characters); - CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, n); - CTFontGetAdvancesForGlyphs(fontRef, kCTFontOrientationHorizontal, glyphs, glyph_advances, n); /* * * Three properties affect the space between characters and words: @@ -682,18 +665,22 @@ A negative value is an error (see Error processing). } } + double tau = 2 * M_PI; + double radToDeg = 360 / tau; CFIndex runEnd = CFArrayGetCount(runs); for (CFIndex r = 0; r < runEnd; r++) { CTRunRef run = CFArrayGetValueAtIndex(runs, r); CFIndex runGlyphCount = CTRunGetGlyphCount(run); - CGPoint positions[runGlyphCount]; + CGSize glyph_advances[runGlyphCount]; + //CGPoint positions[runGlyphCount]; CGGlyph glyphs[runGlyphCount]; // Grab the glyphs, positions, and font - CTRunGetPositions(run, CFRangeMake(0, 0), positions); CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); + //CTRunGetPositions(run, CFRangeMake(0, 0), positions); CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); + CTFontGetAdvancesForGlyphs(runFont, kCTFontOrientationHorizontal, glyphs, glyph_advances, runGlyphCount); for(CFIndex g = 0; g < runGlyphCount; g++) { CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[g], nil); @@ -728,7 +715,7 @@ A negative value is an error (see Error processing). double y = [gc nextY]; double dx = [gc nextDeltaX]; double dy = [gc nextDeltaY]; - NSNumber* r = [gc nextRotation]; + double r = [[gc nextRotation] doubleValue] / radToDeg; advance *= side; charWidth *= side; @@ -764,17 +751,16 @@ A negative value is an error (see Error processing). CGFloat percentConsumed = midPoint / pathLength; CGPoint mid = [_path pointAtPercent:percentConsumed withSlope:&slope]; // Calculate the rotation - double angle = atan(slope.y / slope.x); // + M_PI; - if (slope.x < 0) angle += M_PI; // going left, update the angle + double angle = atan2(slope.y, slope.x); transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(mid.x, mid.y), transform); - transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(angle + [r doubleValue]), transform); + transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(angle + r), transform); transform = CGAffineTransformScale(transform, scaledDirection, side); transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-halfWay, dy + baselineShift), transform); transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, y), transform); } else { - transform = CGAffineTransformConcat(CGAffineTransformMakeRotation([r doubleValue]), transform); transform = CGAffineTransformMakeTranslation(startPoint, y + dy); + transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(r), transform); } transform = CGAffineTransformScale(transform, 1.0, -1.0); @@ -783,6 +769,8 @@ A negative value is an error (see Error processing). } } + CFRelease(attrString); + CFRelease(line); return path; } diff --git a/ios/ViewManagers/RNSVGRenderableManager.m b/ios/ViewManagers/RNSVGRenderableManager.m index 3cfa7bf06..361b95e54 100644 --- a/ios/ViewManagers/RNSVGRenderableManager.m +++ b/ios/ViewManagers/RNSVGRenderableManager.m @@ -32,5 +32,6 @@ - (RNSVGRenderable *)node RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat) RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat) RCT_EXPORT_VIEW_PROPERTY(propList, NSArray) +RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary) @end diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m index 45a8ea675..74cabcf38 100644 --- a/ios/ViewManagers/RNSVGTextManager.m +++ b/ios/ViewManagers/RNSVGTextManager.m @@ -25,6 +25,7 @@ - (RNSVGRenderable *)node RCT_EXPORT_VIEW_PROPERTY(deltaY, NSArray) RCT_EXPORT_VIEW_PROPERTY(positionX, NSArray) RCT_EXPORT_VIEW_PROPERTY(positionY, NSArray) +RCT_EXPORT_VIEW_PROPERTY(rotate, NSArray) RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary) @end From 6cc3f76b2450b1391abd4255d0e8669f78e339b5 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 25 Aug 2017 14:34:00 +0300 Subject: [PATCH 182/198] Fix relative strokeWidth and relativeOnOther usage. --- ios/RNSVGRenderable.m | 3 ++- ios/Shapes/RNSVGCircle.m | 18 ++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 0ad89c797..a02ae4901 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -213,7 +213,8 @@ - (void)renderLayerTo:(CGContextRef)context } if (self.stroke) { - CGContextSetLineWidth(context, [self.strokeWidth floatValue]); + CGFloat width = [self relativeOnOther:self.strokeWidth]; + CGContextSetLineWidth(context, width); CGContextSetLineCap(context, self.strokeLinecap); CGContextSetLineJoin(context, self.strokeLinejoin); RNSVGCGFloatArray dash = self.strokeDasharrayData; diff --git a/ios/Shapes/RNSVGCircle.m b/ios/Shapes/RNSVGCircle.m index 164f5bf8b..f4095e22f 100644 --- a/ios/Shapes/RNSVGCircle.m +++ b/ios/Shapes/RNSVGCircle.m @@ -43,22 +43,8 @@ - (CGPathRef)getPath:(CGContextRef)context CGMutablePathRef path = CGPathCreateMutable(); CGFloat cx = [self relativeOnWidth:self.cx]; CGFloat cy = [self relativeOnHeight:self.cy]; - CGFloat r; - // radius percentage calculate formula: - // radius = sqrt(pow((width*percent), 2) + pow((height*percent), 2)) / sqrt(2) - - if ([RNSVGPercentageConverter isPercentage:self.r]) { - CGFloat radiusPercent = [RNSVGPercentageConverter percentageToFloat:self.r relative:1 offset:0]; - - r = sqrt( - pow([self getContextWidth] * radiusPercent, 2) + - pow([self getContextHeight] * radiusPercent, 2) - ) / sqrt(2); - } else { - r = [self.r floatValue]; - } - - CGPathAddArc(path, nil, cx, cy, r, 0, 2*M_PI, NO); + CGFloat r = [self relativeOnOther:self.r]; + CGPathAddArc(path, nil, cx, cy, r, 0, 2 * M_PI, NO); return (CGPathRef)CFAutorelease(path); } From 8dd795bdd73df54462d785d2d921a3767a5c5798 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 27 Aug 2017 01:09:01 +0300 Subject: [PATCH 183/198] Fix text on a path rendering accuracy --- ios/RNSVG.xcodeproj/project.pbxproj | 8 -------- ios/Text/GlyphContext.h | 1 - ios/Text/GlyphContext.m | 5 ----- ios/Text/RNSVGTSpan.m | 1 + .../UIBezierPath+getTransformAtDistance.h | 15 --------------- .../UIBezierPath+getTransformAtDistance.m | 18 ------------------ 6 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 ios/Utils/UIBezierPath+getTransformAtDistance.h delete mode 100644 ios/Utils/UIBezierPath+getTransformAtDistance.m diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index f0c1707a7..c39b9c653 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -70,8 +70,6 @@ 945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; 945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; 9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */; }; - 9494C4BB1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */; }; - 9494C4BC1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */; }; 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */; }; 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D91F473BCB00D5BCFD /* CoreText.framework */; }; 9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */; }; @@ -313,8 +311,6 @@ 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlignmentBaseline.m; path = Text/AlignmentBaseline.m; sourceTree = ""; }; 945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AlignmentBaseline.h; path = Text/AlignmentBaseline.h; sourceTree = ""; }; 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PerformanceBezier.xcodeproj; path = PerformanceBezier/PerformanceBezier.xcodeproj; sourceTree = ""; }; - 9494C4B91F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath+getTransformAtDistance.h"; path = "Utils/UIBezierPath+getTransformAtDistance.h"; sourceTree = ""; }; - 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+getTransformAtDistance.m"; path = "Utils/UIBezierPath+getTransformAtDistance.m"; sourceTree = ""; }; 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 9494C4D91F473BCB00D5BCFD /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -581,8 +577,6 @@ 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, - 9494C4B91F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.h */, - 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */, ); name = Utils; sourceTree = ""; @@ -730,7 +724,6 @@ 9494C5051F4B5BE800D5BCFD /* FontWeight.m in Sources */, 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */, 7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */, - 9494C4BB1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */, 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */, 10BEC1C21D3F680F00FDCB19 /* RNSVGLinearGradientManager.m in Sources */, 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */, @@ -798,7 +791,6 @@ A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */, A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */, A361E7771EB0C33D00646005 /* RNSVGTextPath.m in Sources */, - 9494C4BC1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */, A361E7781EB0C33D00646005 /* RNSVGUse.m in Sources */, A361E7791EB0C33D00646005 /* RNSVGLinearGradientManager.m in Sources */, 9494C5061F4B5BE800D5BCFD /* FontWeight.m in Sources */, diff --git a/ios/Text/GlyphContext.h b/ios/Text/GlyphContext.h index 1c2099d3d..175151463 100644 --- a/ios/Text/GlyphContext.h +++ b/ios/Text/GlyphContext.h @@ -1,4 +1,3 @@ - #import #import #import "FontData.h" diff --git a/ios/Text/GlyphContext.m b/ios/Text/GlyphContext.m index e8c0102e7..55c16fa5f 100644 --- a/ios/Text/GlyphContext.m +++ b/ios/Text/GlyphContext.m @@ -1,8 +1,3 @@ -// -// Generated by the J2ObjC translator. DO NOT EDIT! -// source: GlyphContext.java -// - #import "GlyphContext.h" #import #import "RNSVGNode.h" diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 048f4c86d..6cddf9d7f 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -801,6 +801,7 @@ - (void)setupTextPath:(CGContextRef)context //_bezierTransformer = [textPath getBezierTransformer]; textPathPath = [textPath getPath]; _path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; + _path = [_path bezierPathByFlatteningPathAndImmutable:YES]; pathLength = [_path length]; return NO; } diff --git a/ios/Utils/UIBezierPath+getTransformAtDistance.h b/ios/Utils/UIBezierPath+getTransformAtDistance.h deleted file mode 100644 index 40161ee64..000000000 --- a/ios/Utils/UIBezierPath+getTransformAtDistance.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// UIBezierPath+getTransformAtDistance.h -// RNSVG -// -// Created by Janne Gylling on 18/08/2017. -// -// - -#import - -@interface UIBezierPath (getTransformAtDistance) - -- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance; - -@end diff --git a/ios/Utils/UIBezierPath+getTransformAtDistance.m b/ios/Utils/UIBezierPath+getTransformAtDistance.m deleted file mode 100644 index 2a16c0fbe..000000000 --- a/ios/Utils/UIBezierPath+getTransformAtDistance.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIBezierPath+getTransformAtDistance.m -// RNSVG -// -// Created by Janne Gylling on 18/08/2017. -// -// - -#import "UIBezierPath+getTransformAtDistance.h" - -@implementation UIBezierPath (getTransformAtDistance) - -- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance -{ - return CGAffineTransformIdentity; -} - -@end From 0dbddcc2b4b375e8ae122b47d9c5941013b9d2d7 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sun, 27 Aug 2017 17:31:03 +0300 Subject: [PATCH 184/198] Fix viewbox scale and translate order/interaction on ios. --- ios/Utils/RNSVGViewBox.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Utils/RNSVGViewBox.m b/ios/Utils/RNSVGViewBox.m index 29651cb8d..dee14d8c7 100644 --- a/ios/Utils/RNSVGViewBox.m +++ b/ios/Utils/RNSVGViewBox.m @@ -91,8 +91,8 @@ + (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSSt // The transform applied to content contained by the element is given by // translate(translate-x, translate-y) scale(scale-x, scale-y). - CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY); - return CGAffineTransformTranslate(transform, translateX, translateY); + CGAffineTransform transform = CGAffineTransformMakeTranslation(translateX, translateY); + return CGAffineTransformScale(transform, scaleX, scaleY); } @end From 04887af278c49a0fa3b7956d2992fb2e71efd279 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 29 Aug 2017 02:34:56 +0300 Subject: [PATCH 185/198] Implement kerning, ligatures, etc., and cleanup; in ios. Implement hasGlyph ligature helper. Export view property setters for: text: textLength, baselineShift, lengthAdjust, alignmentBaseline, textPath: side, method, midLine, spacing. Attempt to fix alignmentBaseline and baselineShift, but both properties are nil at all times, I must be missing something. --- ios/Elements/RNSVGGroup.h | 2 - ios/Elements/RNSVGGroup.m | 10 - ios/RNSVG.xcodeproj/project.pbxproj | 16 - ios/RNSVGNode.m | 2 - ios/Text/RNSVGBezierTransformer.h | 19 - ios/Text/RNSVGBezierTransformer.m | 147 ---- ios/Text/RNSVGGlyphContext.h | 26 - ios/Text/RNSVGGlyphContext.m | 205 ------ ios/Text/RNSVGTSpan.m | 889 +++++++++++++----------- ios/Text/RNSVGText.h | 1 - ios/Text/RNSVGText.m | 21 - ios/Text/RNSVGTextPath.h | 2 - ios/Text/RNSVGTextPath.m | 9 - ios/ViewManagers/RNSVGTextManager.m | 4 + ios/ViewManagers/RNSVGTextPathManager.m | 4 + 15 files changed, 473 insertions(+), 884 deletions(-) delete mode 100644 ios/Text/RNSVGBezierTransformer.h delete mode 100644 ios/Text/RNSVGBezierTransformer.m delete mode 100644 ios/Text/RNSVGGlyphContext.h delete mode 100644 ios/Text/RNSVGGlyphContext.m diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index 11576f0a1..2db11ec49 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -13,7 +13,6 @@ #import "RNSVGSvgView.h" #import "RNSVGPath.h" #import "GlyphContext.h" -#import "RNSVGGlyphContext.h" @interface RNSVGGroup : RNSVGPath @@ -22,7 +21,6 @@ - (void)renderPathTo:(CGContextRef)context; - (void)renderGroupTo:(CGContextRef)context; -- (RNSVGGlyphContext *)getRNSVGGlyphContext; - (GlyphContext *)getGlyphContext; - (void)pushGlyphContext; - (void)popGlyphContext; diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 0515048b9..ed1649c2f 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -11,7 +11,6 @@ @implementation RNSVGGroup { GlyphContext *_glyphContext; - RNSVGGlyphContext *_RNSVGGlyphContext; } - (void)renderLayerTo:(CGContextRef)context @@ -52,17 +51,10 @@ - (void)setupGlyphContext:(CGContextRef)context CGFloat width = CGRectGetWidth(clipBounds); CGFloat height = CGRectGetHeight(clipBounds); - _RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:width - height:height]; _glyphContext = [[GlyphContext alloc] initWithScale:1 width:width height:height]; } -- (RNSVGGlyphContext *)getRNSVGGlyphContext -{ - return _RNSVGGlyphContext; -} - - (GlyphContext *)getGlyphContext { return _glyphContext; @@ -70,13 +62,11 @@ - (GlyphContext *)getGlyphContext - (void)pushGlyphContext { - //[[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font]; [[[self getTextRoot] getGlyphContext] pushContextWithRNSVGGroup:self font:self.font]; } - (void)popGlyphContext { - //[[[self getTextRoot] getRNSVGGlyphContext] popContext]; [[[self getTextRoot] getGlyphContext] popContext]; } diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index c39b9c653..8157a3682 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -14,7 +14,6 @@ 1023B4901D3DF4C40051496D /* RNSVGDefs.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48F1D3DF4C40051496D /* RNSVGDefs.m */; }; 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4921D3DF5060051496D /* RNSVGUse.m */; }; 1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */; }; - 103371321D41C5C90028AF13 /* RNSVGBezierTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */; }; 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */; }; 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; }; 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2861CE71EB7001E90A8 /* RNSVGPath.m */; }; @@ -54,7 +53,6 @@ 7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260CD1E3499BC00A39833 /* RNSVGViewBox.m */; }; 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; }; 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */; }; - 7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */; }; 945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */; }; 945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */; }; 945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */; }; @@ -108,7 +106,6 @@ A361E7711EB0C33D00646005 /* RNSVGCircleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */; }; A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BEC1B91D3F66F500FDCB19 /* RNSVGLinearGradient.m */; }; A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */; }; - A361E7741EB0C33D00646005 /* RNSVGGlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */; }; A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D431CE74E3D00887C2B /* RNSVGEllipse.m */; }; A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2861CE71EB7001E90A8 /* RNSVGPath.m */; }; A361E7771EB0C33D00646005 /* RNSVGTextPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */; }; @@ -125,7 +122,6 @@ A361E7821EB0C33D00646005 /* RNSVGSvgViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D311CE74E3100887C2B /* RNSVGSvgViewManager.m */; }; A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* RNSVGSolidColorBrush.m */; }; A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D291CE74E3100887C2B /* RNSVGPathManager.m */; }; - A361E7851EB0C33D00646005 /* RNSVGBezierTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */; }; A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D2D1CE74E3100887C2B /* RNSVGRenderableManager.m */; }; A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BEC1BB1D3F66F500FDCB19 /* RNSVGRadialGradient.m */; }; A361E7881EB0C33D00646005 /* RNSVGRadialGradientManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BEC1C11D3F680F00FDCB19 /* RNSVGRadialGradientManager.m */; }; @@ -206,8 +202,6 @@ 1023B4921D3DF5060051496D /* RNSVGUse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGUse.m; path = Elements/RNSVGUse.m; sourceTree = ""; }; 1023B4941D3DF57D0051496D /* RNSVGUseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGUseManager.h; sourceTree = ""; }; 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGUseManager.m; sourceTree = ""; }; - 103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGBezierTransformer.m; path = Text/RNSVGBezierTransformer.m; sourceTree = ""; }; - 103371331D41D3400028AF13 /* RNSVGBezierTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGBezierTransformer.h; path = Text/RNSVGBezierTransformer.h; sourceTree = ""; }; 1039D2811CE71EB7001E90A8 /* RNSVGGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGGroup.h; path = Elements/RNSVGGroup.h; sourceTree = ""; }; 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGroup.m; path = Elements/RNSVGGroup.m; sourceTree = ""; }; 1039D2831CE71EB7001E90A8 /* RNSVGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGImage.h; path = Elements/RNSVGImage.h; sourceTree = ""; }; @@ -293,8 +287,6 @@ 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGSymbol.m; path = Elements/RNSVGSymbol.m; sourceTree = ""; }; 7FC260D21E34A12A00A39833 /* RNSVGSymbolManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGSymbolManager.h; sourceTree = ""; }; 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = ""; }; - 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSVGGlyphContext.h; path = Text/RNSVGGlyphContext.h; sourceTree = ""; }; - 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGlyphContext.m; path = Text/RNSVGGlyphContext.m; sourceTree = ""; }; 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */ = {isa = PBXFileReference; lastKnownFileType = folder; path = QuartzBookPack; sourceTree = ""; }; 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Text.m"; path = "QuartzBookPack/TextDrawing/Drawing-Text.m"; sourceTree = ""; }; 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Text.m"; path = "QuartzBookPack/TextDrawing/UIBezierPath+Text.m"; sourceTree = ""; }; @@ -506,10 +498,6 @@ 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */, 9494C4EF1F4B5BE700D5BCFD /* PropHelper.h */, 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */, - 103371331D41D3400028AF13 /* RNSVGBezierTransformer.h */, - 103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */, - 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */, - 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */, 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, 1039D2901CE71EC2001E90A8 /* RNSVGText.m */, 7F08CE9C1E23479700650F83 /* RNSVGTextPath.h */, @@ -718,7 +706,6 @@ 10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */, 9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */, 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */, - 7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */, 9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */, 10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */, 9494C5051F4B5BE800D5BCFD /* FontWeight.m in Sources */, @@ -740,7 +727,6 @@ 10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */, 0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColorBrush.m in Sources */, 10BA0D3A1CE74E3100887C2B /* RNSVGPathManager.m in Sources */, - 103371321D41C5C90028AF13 /* RNSVGBezierTransformer.m in Sources */, 10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */, 10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */, 10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */, @@ -786,7 +772,6 @@ A361E7711EB0C33D00646005 /* RNSVGCircleManager.m in Sources */, A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */, A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */, - A361E7741EB0C33D00646005 /* RNSVGGlyphContext.m in Sources */, 9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */, A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */, A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */, @@ -809,7 +794,6 @@ A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */, 9494C5391F4C44DD00D5BCFD /* FontStyle.m in Sources */, A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */, - A361E7851EB0C33D00646005 /* RNSVGBezierTransformer.m in Sources */, A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */, A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */, 945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */, diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 6c11764b9..a9679be0c 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -10,14 +10,12 @@ #import "RNSVGContainer.h" #import "RNSVGClipPath.h" #import "RNSVGGroup.h" -#import "RNSVGGlyphContext.h" #import "GlyphContext.h" @implementation RNSVGNode { RNSVGGroup *_textRoot; GlyphContext *glyphContext; - RNSVGGlyphContext *RNSVGGlyphContext; BOOL _transparent; CGPathRef _cachedClipPath; RNSVGSvgView *_svgView; diff --git a/ios/Text/RNSVGBezierTransformer.h b/ios/Text/RNSVGBezierTransformer.h deleted file mode 100644 index f89955776..000000000 --- a/ios/Text/RNSVGBezierTransformer.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * 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. - */ - - -#import - -@interface RNSVGBezierTransformer : NSObject - -- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray *)bezierCurves startOffset:(CGFloat)startOffset; -- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance; -- (BOOL)hasReachedEnd; -- (BOOL)hasReachedStart; - -@end diff --git a/ios/Text/RNSVGBezierTransformer.m b/ios/Text/RNSVGBezierTransformer.m deleted file mode 100644 index d8efd1426..000000000 --- a/ios/Text/RNSVGBezierTransformer.m +++ /dev/null @@ -1,147 +0,0 @@ -/** - * 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. - */ - -/** - * based on CurvyText by iosptl: https://github.com/iosptl/ios7ptl/blob/master/ch21-Text/CurvyText/CurvyText/CurvyTextView.m - */ - -#import "RNSVGBezierTransformer.h" - -@implementation RNSVGBezierTransformer -{ - NSArray *_bezierCurves; - int _currentBezierIndex; - CGFloat _startOffset; - CGFloat _lastOffset; - CGFloat _lastRecord; - CGFloat _lastDistance; - CGPoint _lastPoint; - CGPoint _P0; - CGPoint _P1; - CGPoint _P2; - CGPoint _P3; - BOOL _reachedEnd; - BOOL _reachedStart; -} - -- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray *)bezierCurves startOffset:(CGFloat)startOffset -{ - if (self = [super init]) { - _bezierCurves = bezierCurves; - _currentBezierIndex = _lastOffset = _lastRecord = _lastDistance = 0; - _startOffset = startOffset; - [self setControlPoints]; - } - return self; -} - -static CGFloat calculateBezier(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) { - return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3; -} - -- (CGPoint)pointAtOffset:(CGFloat)t { - CGFloat x = calculateBezier(t, _P0.x, _P1.x, _P2.x, _P3.x); - CGFloat y = calculateBezier(t, _P0.y, _P1.y, _P2.y, _P3.y); - return CGPointMake(x, y); -} - -static CGFloat calculateBezierPrime(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) { - return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3; -} - -- (CGFloat)angleAtOffset:(CGFloat)t { - CGFloat dx = calculateBezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x); - CGFloat dy = calculateBezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y); - return atan2(dy, dx); -} - -static CGFloat calculateDistance(CGPoint a, CGPoint b) { - return hypot(a.x - b.x, a.y - b.y); -} - -// Simplistic routine to find the offset along Bezier that is -// `distance` away from `point`. `offset` is the offset used to -// generate `point`, and saves us the trouble of recalculating it -// This routine just walks forward until it finds a point at least -// `distance` away. Good optimizations here would reduce the number -// of guesses, but this is tricky since if we go too far out, the -// curve might loop back on leading to incorrect results. Tuning -// kStep is good start. -- (CGFloat)offsetAtDistance:(CGFloat)distance - fromPoint:(CGPoint)point - offset:(CGFloat)offset { - - const CGFloat kStep = 0.001; // 0.0001 - 0.001 work well - CGFloat newDistance = 0; - CGFloat newOffset = offset + kStep; - while (newDistance <= distance && newOffset < 1.0) { - newOffset += kStep; - newDistance = calculateDistance(point, [self pointAtOffset:newOffset]); - } - - _lastDistance = newDistance; - return newOffset; -} - -- (void)setControlPoints -{ - NSArray *bezier = [_bezierCurves objectAtIndex:_currentBezierIndex++]; - - // set start point - if (bezier.count == 1) { - _lastPoint = _P0 = [[bezier objectAtIndex:0] CGPointValue]; - [self setControlPoints]; - } else if (bezier.count == 3) { - _P1 = [[bezier objectAtIndex:0] CGPointValue]; - _P2 = [[bezier objectAtIndex:1] CGPointValue]; - _P3 = [[bezier objectAtIndex:2] CGPointValue]; - } -} - - -- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance -{ - distance += _startOffset; - _reachedStart = distance >= 0; - if (_reachedEnd || !_reachedStart) { - return CGAffineTransformIdentity; - } - - CGFloat offset = [self offsetAtDistance:distance - _lastRecord - fromPoint:_lastPoint - offset:_lastOffset]; - - if (offset < 1) { - CGPoint glyphPoint = [self pointAtOffset:offset]; - _lastOffset = offset; - _lastPoint = glyphPoint; - _lastRecord = distance; - return CGAffineTransformRotate(CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y), [self angleAtOffset:offset]); - } else if (_bezierCurves.count == _currentBezierIndex) { - _reachedEnd = YES; - return CGAffineTransformIdentity; - } else { - _lastOffset = 0; - _lastPoint = _P0 = _P3; - _lastRecord += _lastDistance; - [self setControlPoints]; - return [self getTransformAtDistance:distance - _startOffset]; - } -} - -- (BOOL)hasReachedEnd -{ - return _reachedEnd; -} - -- (BOOL)hasReachedStart -{ - return _reachedStart; -} - -@end diff --git a/ios/Text/RNSVGGlyphContext.h b/ios/Text/RNSVGGlyphContext.h deleted file mode 100644 index 8fb0cf3b5..000000000 --- a/ios/Text/RNSVGGlyphContext.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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. - */ - -#import -#import -#import "RNSVGPercentageConverter.h" - -@interface RNSVGGlyphContext : NSObject - -- (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height; - -- (CGFloat)getWidth; -- (CGFloat)getHeight; -- (CGFloat)getFontSize; -- (void)pushContext:(NSDictionary *)font; -- (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY; -- (void)popContext; -- (CTFontRef)getGlyphFont; -- (CGPoint)getNextGlyphPoint:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth; - -@end diff --git a/ios/Text/RNSVGGlyphContext.m b/ios/Text/RNSVGGlyphContext.m deleted file mode 100644 index b51d8cffe..000000000 --- a/ios/Text/RNSVGGlyphContext.m +++ /dev/null @@ -1,205 +0,0 @@ -/** - * 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. - */ - - -#import "RNSVGGlyphContext.h" -#import "RNSVGPercentageConverter.h" -#import -#import "RNSVGNode.h" - -@implementation RNSVGGlyphContext -{ - NSMutableArray *_fontContext; - NSMutableArray *_locationContext; - NSMutableArray *_deltaXContext; - NSMutableArray *_deltaYContext; - NSMutableArray *_xContext; - CGPoint _currentLocation; - CGFloat _width; - CGFloat _height; - CGFloat _fontSize; -} - -- (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height -{ - if (self = [super init]) { - _width = width; - _height = height; - _fontContext = [[NSMutableArray alloc] init]; - _locationContext = [[NSMutableArray alloc] init]; - _deltaXContext = [[NSMutableArray alloc] init]; - _deltaYContext = [[NSMutableArray alloc] init]; - _xContext = [[NSMutableArray alloc] init]; - _currentLocation = CGPointZero; - _fontSize = DEFAULT_FONT_SIZE; - } - return self; -} - -- (CGFloat) getWidth -{ - return _width; -} - -- (CGFloat) getHeight { - return _height; -} - -- (CGFloat) getFontSize -{ - return _fontSize; -} - -- (void)pushContext:(NSDictionary *)font -{ - CGPoint location = _currentLocation; - - [_locationContext addObject:[NSValue valueWithCGPoint:location]]; - [_fontContext addObject:font ? font : @{}]; - [_xContext addObject:[NSNumber numberWithFloat:location.x]]; - _currentLocation = location; -} - -- (void)pushContext:(NSDictionary *)font deltaX:(NSArray *)deltaX deltaY:(NSArray *)deltaY positionX:(NSArray *)positionX positionY:(NSArray *)positionY -{ - CGPoint location = _currentLocation; - - if (positionX) { - location.x = [RNSVGPercentageConverter stringToFloat:[positionX firstObject] relative:_width offset:0]; - } - - if (positionY) { - location.y = [RNSVGPercentageConverter stringToFloat:[positionY firstObject] relative:_height offset:0]; - } - - [_locationContext addObject:[NSValue valueWithCGPoint:location]]; - [_fontContext addObject:font ? font : @{}]; - [_deltaXContext addObject:deltaX ? deltaX : @[]]; - [_deltaYContext addObject:deltaY ? deltaY : @[]]; - [_xContext addObject:[NSNumber numberWithFloat:location.x]]; - _currentLocation = location; -} - -- (void)popContext -{ - NSNumber *x = [_xContext lastObject]; - [_fontContext removeLastObject]; - [_locationContext removeLastObject]; - [_deltaXContext removeLastObject]; - [_deltaYContext removeLastObject]; - [_xContext removeLastObject]; - - if (_xContext.count) { - [_xContext replaceObjectAtIndex:_xContext.count - 1 withObject:x]; - } - - if (_locationContext.count) { - _currentLocation = [[_locationContext lastObject] CGPointValue]; - _currentLocation.x = [x floatValue]; - [_locationContext replaceObjectAtIndex:_locationContext.count - 1 - withObject:[NSValue valueWithCGPoint:_currentLocation]]; - } -} - -- (CGPoint)getNextGlyphPoint:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth -{ - CGPoint currentLocation = _currentLocation; - NSNumber *dx = [self getNextDelta:_deltaXContext]; - currentLocation.x += [dx floatValue]; - - NSNumber *dy = [self getNextDelta:_deltaYContext]; - currentLocation.y += [dy floatValue]; - - for (NSUInteger i = 0; i < _locationContext.count; i++) { - CGPoint point = [[_locationContext objectAtIndex:i] CGPointValue]; - point.x += [dx floatValue]; - point.y += [dy floatValue]; - [_locationContext replaceObjectAtIndex:i withObject:[NSValue valueWithCGPoint:point]]; - } - - _currentLocation = currentLocation; - NSNumber *x = [NSNumber numberWithFloat:currentLocation.x + offset.x + glyphWidth]; - [_xContext replaceObjectAtIndex:_xContext.count - 1 withObject:x]; - return CGPointMake(currentLocation.x + offset.x, currentLocation.y + offset.y); -} - -- (NSNumber *)getNextDelta:(NSMutableArray *)deltaContext -{ - NSNumber *value; - NSUInteger index = deltaContext.count; - for (NSArray *delta in [deltaContext reverseObjectEnumerator]) { - index--; - if (value == nil) { - value = [delta firstObject]; - } - - if (delta.count) { - NSMutableArray *mutableDelta = [delta mutableCopy]; - [mutableDelta removeObjectAtIndex:0]; - [deltaContext replaceObjectAtIndex:index withObject:[mutableDelta copy]]; - } - } - - return value; -} - -- (CTFontRef)getGlyphFont -{ - NSString *fontFamily; - NSNumber *fontSize; - NSString *fontWeight; - NSString *fontStyle; - NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; - f.numberStyle = NSNumberFormatterDecimalStyle; - - for (NSDictionary *font in [_fontContext reverseObjectEnumerator]) { - if (!fontFamily) { - fontFamily = font[@"fontFamily"]; - } - - if (fontSize == nil) { - fontSize = [f numberFromString:font[@"fontSize"]]; - } - - if (!fontWeight) { - fontWeight = font[@"fontWeight"]; - } - if (!fontStyle) { - fontStyle = font[@"fontStyle"]; - } - - if (fontFamily && fontSize && fontWeight && fontStyle) { - break; - } - } - - BOOL fontFamilyFound = NO; - NSArray *supportedFontFamilyNames = [UIFont familyNames]; - - if ([supportedFontFamilyNames containsObject:fontFamily]) { - fontFamilyFound = YES; - } else { - for (NSString *fontFamilyName in supportedFontFamilyNames) { - if ([[UIFont fontNamesForFamilyName: fontFamilyName] containsObject:fontFamily]) { - fontFamilyFound = YES; - break; - } - } - } - fontFamily = fontFamilyFound ? fontFamily : nil; - - return (__bridge CTFontRef)[RCTFont updateFont:nil - withFamily:fontFamily - size:fontSize - weight:fontWeight - style:fontStyle - variant:nil - scaleMultiplier:1.0]; -} - -@end diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 6cddf9d7f..79b519aea 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -7,7 +7,6 @@ */ #import #import "RNSVGTSpan.h" -#import "RNSVGBezierTransformer.h" #import "RNSVGText.h" #import "RNSVGTextPath.h" #import "UIBezierPath+Text.h" @@ -15,13 +14,13 @@ @implementation RNSVGTSpan { - RNSVGBezierTransformer *_bezierTransformer; CGFloat startOffset; UIBezierPath *_path; CGPathRef _cache; CGFloat pathLength; RNSVGTextPath * textPath; RNSVGPath * textPathPath; + bool isClosed; } - (void)setContent:(NSString *)content @@ -103,6 +102,10 @@ - (CGMutablePathRef)getLinePath:(NSString *)str GlyphContext* gc = [[self getTextRoot] getGlyphContext]; FontData* font = [gc getFont]; NSUInteger n = str.length; + bool ligature[n]; + for (int i = 0; i < n; i++){ + ligature[i] = NO; + } /* * * Three properties affect the space between characters and words: @@ -122,7 +125,7 @@ - (CGMutablePathRef)getLinePath:(NSString *)str * or decrease the space between typographic character units in order to justify text. * * */ - // TODO double kerning = font->kerning; + double kerning = font->kerning; double wordSpacing = font->wordSpacing; double letterSpacing = font->letterSpacing; bool autoKerning = !font->manualKerning; @@ -174,7 +177,7 @@ the same as the default space (e.g. when letter-spacing has a non-default value, user agents should not apply optional ligatures. https://www.w3.org/TR/css-text-3/#letter-spacing-property */ - // TODO bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == FontVariantLigaturesNormal; + bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == FontVariantLigaturesNormal; /* For OpenType fonts, discretionary ligatures include those enabled by @@ -233,464 +236,451 @@ along with localized forms (OpenType feature: locl), vertical alternates (OpenType feature: vert) must be enabled. */ // OpenType.js font data - // TODO NSDictionary * fontData = font->fontData; + NSDictionary * fontData = font->fontData; - /* - float advances[] = new float[length]; - paint.getTextWidths(line, advances); - */ - /* - This would give both advances and textMeasure in one call / looping over the text - double textMeasure = paint.getTextRunAdvances(line, 0, length, 0, length, true, advances, 0); - */ - /* - Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ - and property text-anchor. - - For text-anchor:start, startpoint-on-the-path is the point - on the path which represents the point on the path which is ‘startOffset’ distance - along the path from the start of the path, calculated using the user agent's distance - along the path algorithm. - - For text-anchor:middle, startpoint-on-the-path is the point - on the path which represents the point on the path which is [ ‘startOffset’ minus half - of the total advance values for all of the glyphs in the ‘textPath’ element ] distance - along the path from the start of the path, calculated using the user agent's distance - along the path algorithm. - - For text-anchor:end, startpoint-on-the-path is the point on - the path which represents the point on the path which is [ ‘startOffset’ minus the - total advance values for all of the glyphs in the ‘textPath’ element ]. - - Before rendering the first glyph, the horizontal component of the startpoint-on-the-path - is adjusted to take into account various horizontal alignment text properties and - attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. - */ - enum TextAnchor textAnchor = font->textAnchor; - CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); - double textMeasure = CGRectGetWidth(textBounds); - double offset = getTextAnchorOffset(textAnchor, textMeasure); - - bool hasTextPath = _path != nil; - bool isClosed = false; - - int side = 1; - double startOfRendering = 0; - double endOfRendering = pathLength; - double fontSize = [gc getFontSize]; - bool sharpMidLine = false; - if (hasTextPath) { - sharpMidLine = TextPathMidLineFromString([textPath midLine]) == TextPathMidLineSharp; + double tau = 2 * M_PI; + double radToDeg = 360 / tau; + CFIndex runEnd = CFArrayGetCount(runs); + for (CFIndex r = 0; r < runEnd; r++) { + CTRunRef run = CFArrayGetValueAtIndex(runs, r); + + CFIndex runGlyphCount = CTRunGetGlyphCount(run); + CGSize advances[runGlyphCount]; + CGGlyph glyphs[runGlyphCount]; + + // Grab the glyphs and font + CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); + CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); + CTFontGetAdvancesForGlyphs(runFont, kCTFontOrientationHorizontal, glyphs, advances, runGlyphCount); + /* - Name - side - Value - left | right - initial value - left - Animatable - yes + Determine the startpoint-on-the-path for the first glyph using attribute ‘startOffset’ + and property text-anchor. + + For text-anchor:start, startpoint-on-the-path is the point + on the path which represents the point on the path which is ‘startOffset’ distance + along the path from the start of the path, calculated using the user agent's distance + along the path algorithm. + + For text-anchor:middle, startpoint-on-the-path is the point + on the path which represents the point on the path which is [ ‘startOffset’ minus half + of the total advance values for all of the glyphs in the ‘textPath’ element ] distance + along the path from the start of the path, calculated using the user agent's distance + along the path algorithm. + + For text-anchor:end, startpoint-on-the-path is the point on + the path which represents the point on the path which is [ ‘startOffset’ minus the + total advance values for all of the glyphs in the ‘textPath’ element ]. + + Before rendering the first glyph, the horizontal component of the startpoint-on-the-path + is adjusted to take into account various horizontal alignment text properties and + attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. + */ + enum TextAnchor textAnchor = font->textAnchor; + CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); + double textMeasure = CGRectGetWidth(textBounds); + double offset = getTextAnchorOffset(textAnchor, textMeasure); + + bool hasTextPath = _path != nil; + + int side = 1; + double startOfRendering = 0; + double endOfRendering = pathLength; + double fontSize = [gc getFontSize]; + bool sharpMidLine = false; + if (hasTextPath) { + sharpMidLine = TextPathMidLineFromString([textPath midLine]) == TextPathMidLineSharp; + /* + Name + side + Value + left | right + initial value + left + Animatable + yes - Determines the side of the path the text is placed on - (relative to the path direction). + Determines the side of the path the text is placed on + (relative to the path direction). - Specifying a value of right effectively reverses the path. + Specifying a value of right effectively reverses the path. - Added in SVG 2 to allow text either inside or outside closed subpaths - and basic shapes (e.g. rectangles, circles, and ellipses). + Added in SVG 2 to allow text either inside or outside closed subpaths + and basic shapes (e.g. rectangles, circles, and ellipses). + + Adding 'side' was resolved at the Sydney (2015) meeting. + */ + side = TextPathSideFromString([textPath side]) == TextPathSideRight ? -1 : 1; + /* + Name + startOffset + Value + | | + initial value + 0 + Animatable + yes + + An offset from the start of the path for the initial current text position, + calculated using the user agent's distance along the path algorithm, + after converting the path to the ‘textPath’ element's coordinate system. + + If a other than a percentage is given, then the ‘startOffset’ + represents a distance along the path measured in the current user coordinate + system for the ‘textPath’ element. + + If a percentage is given, then the ‘startOffset’ represents a percentage + distance along the entire path. Thus, startOffset="0%" indicates the start + point of the path and startOffset="100%" indicates the end point of the path. + + Negative values and values larger than the path length (e.g. 150%) are allowed. + + Any typographic characters with mid-points that are not on the path are not rendered + + For paths consisting of a single closed subpath (including an equivalent path for a + basic shape), typographic characters are rendered along one complete circuit of the + path. The text is aligned as determined by the text-anchor property to a position + along the path set by the ‘startOffset’ attribute. + + For the start (end) value, the text is rendered from the start (end) of the line + until the initial position along the path is reached again. + + For the middle, the text is rendered from the middle point in both directions until + a point on the path equal distance in both directions from the initial position on + the path is reached. + */ + double absoluteStartOffset = [PropHelper fromRelativeWithNSString:textPath.startOffset + relative:pathLength + offset:0 + scale:1 + fontSize:fontSize]; + offset += absoluteStartOffset; + if (isClosed) { + double halfPathDistance = pathLength / 2; + startOfRendering = absoluteStartOffset + (textAnchor == TextAnchorMiddle ? -halfPathDistance : 0); + endOfRendering = startOfRendering + pathLength; + } + /* + TextPathSpacing spacing = textPath.getSpacing(); + if (spacing == TextPathSpacing.auto) { + // Hmm, what to do here? + // https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute + } + */ + } - Adding 'side' was resolved at the Sydney (2015) meeting. - */ - side = TextPathSideFromString([textPath side]) == TextPathSideRight ? -1 : 1; /* Name - startOffset + method Value - | | + align | stretch initial value - 0 + align Animatable yes + Indicates the method by which text should be rendered along the path. - An offset from the start of the path for the initial current text position, - calculated using the user agent's distance along the path algorithm, - after converting the path to the ‘textPath’ element's coordinate system. - - If a other than a percentage is given, then the ‘startOffset’ - represents a distance along the path measured in the current user coordinate - system for the ‘textPath’ element. - - If a percentage is given, then the ‘startOffset’ represents a percentage - distance along the entire path. Thus, startOffset="0%" indicates the start - point of the path and startOffset="100%" indicates the end point of the path. + A value of align indicates that the typographic character should be rendered using + simple 2×3 matrix transformations such that there is no stretching/warping of the + typographic characters. Typically, supplemental rotation, scaling and translation + transformations are done for each typographic characters to be rendered. - Negative values and values larger than the path length (e.g. 150%) are allowed. + As a result, with align, in fonts where the typographic characters are designed to be + connected (e.g., cursive fonts), the connections may not align properly when text is + rendered along a path. - Any typographic characters with mid-points that are not on the path are not rendered + A value of stretch indicates that the typographic character outlines will be converted + into paths, and then all end points and control points will be adjusted to be along the + perpendicular vectors from the path, thereby stretching and possibly warping the glyphs. - For paths consisting of a single closed subpath (including an equivalent path for a - basic shape), typographic characters are rendered along one complete circuit of the - path. The text is aligned as determined by the text-anchor property to a position - along the path set by the ‘startOffset’ attribute. + With this approach, connected typographic characters, such as in cursive scripts, + will maintain their connections. (Non-vertical straight path segments should be + converted to Bézier curves in such a way that horizontal straight paths have an + (approximately) constant offset from the path along which the typographic characters + are rendered.) - For the start (end) value, the text is rendered from the start (end) of the line - until the initial position along the path is reached again. + TODO implement stretch + */ - For the middle, the text is rendered from the middle point in both directions until - a point on the path equal distance in both directions from the initial position on - the path is reached. + /* + Name Value Initial value Animatable + textLength | | See below yes + + The author's computation of the total sum of all of the advance values that correspond + to character data within this element, including the advance value on the glyph + (horizontal or vertical), the effect of properties letter-spacing and word-spacing and + adjustments due to attributes ‘dx’ and ‘dy’ on this ‘text’ or ‘tspan’ element or any + descendants. This value is used to calibrate the user agent's own calculations with + that of the author. + + The purpose of this attribute is to allow the author to achieve exact alignment, + in visual rendering order after any bidirectional reordering, for the first and + last rendered glyphs that correspond to this element; thus, for the last rendered + character (in visual rendering order after any bidirectional reordering), + any supplemental inter-character spacing beyond normal glyph advances are ignored + (in most cases) when the user agent determines the appropriate amount to expand/compress + the text string to fit within a length of ‘textLength’. + + If attribute ‘textLength’ is specified on a given element and also specified on an + ancestor, the adjustments on all character data within this element are controlled by + the value of ‘textLength’ on this element exclusively, with the possible side-effect + that the adjustment ratio for the contents of this element might be different than the + adjustment ratio used for other content that shares the same ancestor. The user agent + must assume that the total advance values for the other content within that ancestor is + the difference between the advance value on that ancestor and the advance value for + this element. + + This attribute is not intended for use to obtain effects such as shrinking or + expanding text. + + A negative value is an error (see Error processing). + + The ‘textLength’ attribute is only applied when the wrapping area is not defined by the + TODO shape-inside or the inline-size properties. It is also not applied for any ‘text’ or + TODO ‘tspan’ element that has forced line breaks (due to a white-space value of pre or + pre-line). + + If the attribute is not specified anywhere within a ‘text’ element, the effect is as if + the author's computation exactly matched the value calculated by the user agent; + thus, no advance adjustments are made. */ - double absoluteStartOffset = [PropHelper fromRelativeWithNSString:textPath.startOffset - relative:pathLength - offset:0 - scale:1 - fontSize:fontSize]; - offset += absoluteStartOffset; - if (isClosed) { - double halfPathDistance = pathLength / 2; - startOfRendering = absoluteStartOffset + (textAnchor == TextAnchorMiddle ? -halfPathDistance : 0); - endOfRendering = startOfRendering + pathLength; + double scaleSpacingAndGlyphs = 1; + NSString *mTextLength = [self textLength]; + enum TextLengthAdjust mLengthAdjust = TextLengthAdjustFromString([self lengthAdjust]); + if (mTextLength != nil) { + double author = [PropHelper fromRelativeWithNSString:mTextLength + relative:[gc getWidth] + offset:0 + scale:1 + fontSize:fontSize]; + if (author < 0) { + NSException *e = [NSException + exceptionWithName:@"NegativeTextLength" + reason:@"Negative textLength value" + userInfo:nil]; + @throw e; + } + switch (mLengthAdjust) { + default: + case TextLengthAdjustSpacing: + letterSpacing += (author - textMeasure) / (n - 1); + break; + case TextLengthAdjustSpacingAndGlyphs: + scaleSpacingAndGlyphs = author / textMeasure; + break; + } } + double scaledDirection = scaleSpacingAndGlyphs * side; + /* - TextPathSpacing spacing = textPath.getSpacing(); - if (spacing == TextPathSpacing.auto) { - // Hmm, what to do here? - // https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute - } - */ - } + https://developer.mozilla.org/en/docs/Web/CSS/vertical-align + https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html + https://www.microsoft.com/typography/otspec/base.htm + http://apike.ca/prog_svg_text_style.html + https://www.w3schools.com/tags/canvas_textbaseline.asp + http://vanseodesign.com/web-design/svg-text-baseline-alignment/ + https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align + https://tympanus.net/codrops/css_reference/vertical-align/ - /* - Name - method - Value - align | stretch - initial value - align - Animatable - yes - Indicates the method by which text should be rendered along the path. - - A value of align indicates that the typographic character should be rendered using - simple 2×3 matrix transformations such that there is no stretching/warping of the - typographic characters. Typically, supplemental rotation, scaling and translation - transformations are done for each typographic characters to be rendered. - - As a result, with align, in fonts where the typographic characters are designed to be - connected (e.g., cursive fonts), the connections may not align properly when text is - rendered along a path. - - A value of stretch indicates that the typographic character outlines will be converted - into paths, and then all end points and control points will be adjusted to be along the - perpendicular vectors from the path, thereby stretching and possibly warping the glyphs. - - With this approach, connected typographic characters, such as in cursive scripts, - will maintain their connections. (Non-vertical straight path segments should be - converted to Bézier curves in such a way that horizontal straight paths have an - (approximately) constant offset from the path along which the typographic characters - are rendered.) - - TODO implement stretch - */ + https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty + 11.10.2.6. The ‘alignment-baseline’ property - /* - Name Value Initial value Animatable - textLength | | See below yes - - The author's computation of the total sum of all of the advance values that correspond - to character data within this element, including the advance value on the glyph - (horizontal or vertical), the effect of properties letter-spacing and word-spacing and - adjustments due to attributes ‘dx’ and ‘dy’ on this ‘text’ or ‘tspan’ element or any - descendants. This value is used to calibrate the user agent's own calculations with - that of the author. - - The purpose of this attribute is to allow the author to achieve exact alignment, - in visual rendering order after any bidirectional reordering, for the first and - last rendered glyphs that correspond to this element; thus, for the last rendered - character (in visual rendering order after any bidirectional reordering), - any supplemental inter-character spacing beyond normal glyph advances are ignored - (in most cases) when the user agent determines the appropriate amount to expand/compress - the text string to fit within a length of ‘textLength’. - - If attribute ‘textLength’ is specified on a given element and also specified on an - ancestor, the adjustments on all character data within this element are controlled by - the value of ‘textLength’ on this element exclusively, with the possible side-effect - that the adjustment ratio for the contents of this element might be different than the - adjustment ratio used for other content that shares the same ancestor. The user agent - must assume that the total advance values for the other content within that ancestor is - the difference between the advance value on that ancestor and the advance value for - this element. - - This attribute is not intended for use to obtain effects such as shrinking or - expanding text. - - A negative value is an error (see Error processing). - - The ‘textLength’ attribute is only applied when the wrapping area is not defined by the - TODO shape-inside or the inline-size properties. It is also not applied for any ‘text’ or - TODO ‘tspan’ element that has forced line breaks (due to a white-space value of pre or - pre-line). - - If the attribute is not specified anywhere within a ‘text’ element, the effect is as if - the author's computation exactly matched the value calculated by the user agent; - thus, no advance adjustments are made. - */ - double scaleSpacingAndGlyphs = 1; - NSString *mTextLength = [self textLength]; - enum TextLengthAdjust mLengthAdjust = TextLengthAdjustFromString([self lengthAdjust]); - // TODO canvasWidth - double canvasWidth = 0; - if (mTextLength != nil) { - double author = [PropHelper fromRelativeWithNSString:mTextLength - relative:canvasWidth - offset:0 - scale:1 - fontSize:fontSize]; - if (author < 0) { - NSException *e = [NSException - exceptionWithName:@"NegativeTextLength" - reason:@"Negative textLength value" - userInfo:nil]; - @throw e; - } - switch (mLengthAdjust) { - default: - case TextLengthAdjustSpacing: - letterSpacing += (author - textMeasure) / (n - 1); - break; - case TextLengthAdjustSpacingAndGlyphs: - scaleSpacingAndGlyphs = author / textMeasure; - break; - } - } - double scaledDirection = scaleSpacingAndGlyphs * side; + This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3] + https://drafts.csswg.org/css-inline/#propdef-alignment-baseline - /* - https://developer.mozilla.org/en/docs/Web/CSS/vertical-align - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html - https://www.microsoft.com/typography/otspec/base.htm - http://apike.ca/prog_svg_text_style.html - https://www.w3schools.com/tags/canvas_textbaseline.asp - http://vanseodesign.com/web-design/svg-text-baseline-alignment/ - https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align - https://tympanus.net/codrops/css_reference/vertical-align/ + The vertical-align property shorthand should be preferred in new content. - https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty - 11.10.2.6. The ‘alignment-baseline’ property + SVG 2 introduces some changes to the definition of this property. + In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed. + For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and + 'text-after-edge' should be mapped to 'text-bottom'. - This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3] - https://drafts.csswg.org/css-inline/#propdef-alignment-baseline + Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. + */ + /* + CGRect fontBounds = CTFontGetBoundingBox(fontRef); + double textHeight = CGRectGetHeight(textBounds); + double fontWidth = CGRectGetWidth(textBounds); + CGPoint fontOrigin = fontBounds.origin; + + CGFloat fontMinX = fontOrigin.x; + CGFloat fontMinY = fontOrigin.y; + CGFloat fontMaxX = fontMinX + fontWidth; + CGFloat fontMaxY = fontMinY + textHeight; + */ + // TODO + double descenderDepth = CTFontGetDescent(fontRef); + double bottom = descenderDepth + CTFontGetLeading(fontRef); + double ascenderHeight = CTFontGetAscent(fontRef); + double top = ascenderHeight; + double totalHeight = top + bottom; + double baselineShift = 0; + // TODO, alignmentBaseline and baselineShift are always nil for some reason? + NSString *baselineShiftString = [self baselineShift]; + enum AlignmentBaseline baseline = AlignmentBaselineFromString([self alignmentBaseline]); + if (baseline != AlignmentBaselineBaseline) { + // TODO alignment-baseline, test / verify behavior + // TODO get per glyph baselines from font baseline table, for high-precision alignment + CGFloat xHeight = CTFontGetXHeight(fontRef); + switch (baseline) { + // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + default: + case AlignmentBaselineBaseline: + // Use the dominant baseline choice of the parent. + // Match the box’s corresponding baseline to that of its parent. + baselineShift = 0; + break; - The vertical-align property shorthand should be preferred in new content. + case AlignmentBaselineTextBottom: + case AlignmentBaselineAfterEdge: + case AlignmentBaselineTextAfterEdge: + // Match the bottom of the box to the bottom of the parent’s content area. + // text-after-edge = text-bottom + // text-after-edge = descender depth + baselineShift = -descenderDepth; + break; - SVG 2 introduces some changes to the definition of this property. - In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed. - For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and - 'text-after-edge' should be mapped to 'text-bottom'. + case AlignmentBaselineAlphabetic: + // Match the box’s alphabetic baseline to that of its parent. + // alphabetic = 0 + baselineShift = 0; + break; - Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property. - */ - //Paint.FontMetrics fm = paint.getFontMetrics(); - double top = 0;//-fm.top; - double bottom = 0;//fm.bottom; - double ascenderHeight = 0;//-fm.ascent; - double descenderDepth = 0;//fm.descent; - double totalHeight = top + bottom; - double baselineShift = 0; - NSString *baselineShiftString = [self baselineShift]; - enum AlignmentBaseline baseline = AlignmentBaselineFromString([self alignmentBaseline]); - if (baseline != AlignmentBaselineBaseline) { - // TODO alignment-baseline, test / verify behavior - // TODO get per glyph baselines from font baseline table, for high-precision alignment - switch (baseline) { - // https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling - default: - case AlignmentBaselineBaseline: - // Use the dominant baseline choice of the parent. - // Match the box’s corresponding baseline to that of its parent. - baselineShift = 0; - break; - - case AlignmentBaselineTextBottom: - case AlignmentBaselineAfterEdge: - case AlignmentBaselineTextAfterEdge: - // Match the bottom of the box to the bottom of the parent’s content area. - // text-after-edge = text-bottom - // text-after-edge = descender depth - baselineShift = -descenderDepth; - break; - - case AlignmentBaselineAlphabetic: - // Match the box’s alphabetic baseline to that of its parent. - // alphabetic = 0 - baselineShift = 0; - break; - - case AlignmentBaselineIdeographic: - // Match the box’s ideographic character face under-side baseline to that of its parent. - // ideographic = descender depth - baselineShift = -descenderDepth; - break; - - case AlignmentBaselineMiddle: - // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. TODO - // middle = x height / 2 - //Rect bounds = new Rect(); - // this will just retrieve the bounding rect for 'x' - //paint.getTextBounds("x", 0, 1, bounds); - //int xHeight = bounds.height(); - //baselineShift = xHeight / 2; - break; - - case AlignmentBaselineCentral: - // Match the box’s central baseline to the central baseline of its parent. - // central = (ascender height - descender depth) / 2 - baselineShift = (ascenderHeight - descenderDepth) / 2; - break; - - case AlignmentBaselineMathematical: - // Match the box’s mathematical baseline to that of its parent. - // Hanging and mathematical baselines - // There are no obvious formulas to calculate the position of these baselines. - // At the time of writing FOP puts the hanging baseline at 80% of the ascender - // height and the mathematical baseline at 50%. - baselineShift = 0.5 * ascenderHeight; - break; - - case AlignmentBaselineHanging: - baselineShift = 0.8 * ascenderHeight; - break; - - case AlignmentBaselineTextTop: - case AlignmentBaselineBeforeEdge: - case AlignmentBaselineTextBeforeEdge: - // Match the top of the box to the top of the parent’s content area. - // text-before-edge = text-top - // text-before-edge = ascender height - baselineShift = ascenderHeight; - break; - - case AlignmentBaselineBottom: - // Align the top of the aligned subtree with the top of the line box. - baselineShift = bottom; - break; - - case AlignmentBaselineCenter: - // Align the center of the aligned subtree with the center of the line box. - baselineShift = totalHeight / 2; - break; - - case AlignmentBaselineTop: - // Align the bottom of the aligned subtree with the bottom of the line box. - baselineShift = top; - break; - } - /* - 2.2.2. Alignment Shift: baseline-shift longhand + case AlignmentBaselineIdeographic: + // Match the box’s ideographic character face under-side baseline to that of its parent. + // ideographic = descender depth + baselineShift = -descenderDepth; + break; - This property specifies by how much the box is shifted up from its alignment point. - It does not apply when alignment-baseline is top or bottom. + case AlignmentBaselineMiddle: + // Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. TODO + // middle = x height / 2 + baselineShift = xHeight / 2; + break; - Authors should use the vertical-align shorthand instead of this property. + case AlignmentBaselineCentral: + // Match the box’s central baseline to the central baseline of its parent. + // central = (ascender height - descender depth) / 2 + baselineShift = (ascenderHeight - descenderDepth) / 2; + break; - Values have the following meanings: + case AlignmentBaselineMathematical: + // Match the box’s mathematical baseline to that of its parent. + // Hanging and mathematical baselines + // There are no obvious formulas to calculate the position of these baselines. + // At the time of writing FOP puts the hanging baseline at 80% of the ascender + // height and the mathematical baseline at 50%. + baselineShift = 0.5 * ascenderHeight; + break; - - Raise (positive value) or lower (negative value) by the specified length. - - Raise (positive value) or lower (negative value) by the specified percentage of the line-height. - TODO sub - Lower by the offset appropriate for subscripts of the parent’s box. - (The UA should use the parent’s font data to find this offset whenever possible.) - TODO super - Raise by the offset appropriate for superscripts of the parent’s box. - (The UA should use the parent’s font data to find this offset whenever possible.) + case AlignmentBaselineHanging: + baselineShift = 0.8 * ascenderHeight; + break; - User agents may additionally support the keyword baseline as computing to 0 - if is necessary for them to support legacy SVG content. - Issue: We would prefer to remove this, - and are looking for feedback from SVG user agents as to whether it’s necessary. + case AlignmentBaselineTextTop: + case AlignmentBaselineBeforeEdge: + case AlignmentBaselineTextBeforeEdge: + // Match the top of the box to the top of the parent’s content area. + // text-before-edge = text-top + // text-before-edge = ascender height + baselineShift = ascenderHeight; + break; - https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift - */ - if (baselineShiftString != nil) { - switch (baseline) { - case AlignmentBaselineTop: case AlignmentBaselineBottom: + // Align the top of the aligned subtree with the top of the line box. + baselineShift = bottom; break; - default: - if ([baselineShiftString isEqualToString:@"sub"]) { - // TODO - /* - if (fontData != nil && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) { - int unitsPerEm = fontData.getInt("unitsPerEm"); - ReadableMap tables = fontData.getMap("tables"); - if (tables.hasKey("os2")) { - ReadableMap os2 = tables.getMap("os2"); - if (os2.hasKey("ySubscriptYOffset")) { - double subOffset = os2.getDouble("ySubscriptYOffset"); - baselineShift += fontSize * subOffset / unitsPerEm; - } - } - } - */ - } else if ([baselineShiftString isEqualToString:@"super"]) { - // TODO - /* - if (fontData != nil && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) { - int unitsPerEm = fontData.getInt("unitsPerEm"); - ReadableMap tables = fontData.getMap("tables"); - if (tables.hasKey("os2")) { - ReadableMap os2 = tables.getMap("os2"); - if (os2.hasKey("ySuperscriptYOffset")) { - double superOffset = os2.getDouble("ySuperscriptYOffset"); - baselineShift -= fontSize * superOffset / unitsPerEm; - } - } - } - */ - } else if ([baselineShiftString isEqualToString:@"baseline"]) { - } else { - baselineShift -= [PropHelper fromRelativeWithNSString:baselineShiftString - relative:fontSize - offset:0 - scale:1 - fontSize:fontSize]; + case AlignmentBaselineCenter: + // Align the center of the aligned subtree with the center of the line box. + baselineShift = totalHeight / 2; + break; - } + case AlignmentBaselineTop: + // Align the bottom of the aligned subtree with the bottom of the line box. + baselineShift = top; break; } - } - } + /* + 2.2.2. Alignment Shift: baseline-shift longhand - double tau = 2 * M_PI; - double radToDeg = 360 / tau; - CFIndex runEnd = CFArrayGetCount(runs); - for (CFIndex r = 0; r < runEnd; r++) { - CTRunRef run = CFArrayGetValueAtIndex(runs, r); + This property specifies by how much the box is shifted up from its alignment point. + It does not apply when alignment-baseline is top or bottom. - CFIndex runGlyphCount = CTRunGetGlyphCount(run); - CGSize glyph_advances[runGlyphCount]; - //CGPoint positions[runGlyphCount]; - CGGlyph glyphs[runGlyphCount]; + Authors should use the vertical-align shorthand instead of this property. - // Grab the glyphs, positions, and font - CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); - //CTRunGetPositions(run, CFRangeMake(0, 0), positions); - CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); - CTFontGetAdvancesForGlyphs(runFont, kCTFontOrientationHorizontal, glyphs, glyph_advances, runGlyphCount); + Values have the following meanings: + + + Raise (positive value) or lower (negative value) by the specified length. + + Raise (positive value) or lower (negative value) by the specified percentage of the line-height. + TODO sub + Lower by the offset appropriate for subscripts of the parent’s box. + (The UA should use the parent’s font data to find this offset whenever possible.) + TODO super + Raise by the offset appropriate for superscripts of the parent’s box. + (The UA should use the parent’s font data to find this offset whenever possible.) + + User agents may additionally support the keyword baseline as computing to 0 + if is necessary for them to support legacy SVG content. + Issue: We would prefer to remove this, + and are looking for feedback from SVG user agents as to whether it’s necessary. + + https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift + */ + if (baselineShiftString != nil && fontData != nil) { + switch (baseline) { + case AlignmentBaselineTop: + case AlignmentBaselineBottom: + break; + + default: + if ([baselineShiftString isEqualToString:@"sub"]) { + // TODO + NSDictionary* tables = [fontData objectForKey:@"tables"]; + NSNumber* unitsPerEm = [fontData objectForKey:@"unitsPerEm"]; + NSDictionary* os2 = [tables objectForKey:@"os2"]; + NSNumber* ySubscriptYOffset = [os2 objectForKey:@"ySubscriptYOffset"]; + if (ySubscriptYOffset) { + double subOffset = [ySubscriptYOffset doubleValue]; + baselineShift += fontSize * subOffset / [unitsPerEm doubleValue]; + } + } else if ([baselineShiftString isEqualToString:@"super"]) { + // TODO + NSDictionary* tables = [fontData objectForKey:@"tables"]; + NSNumber* unitsPerEm = [fontData objectForKey:@"unitsPerEm"]; + NSDictionary* os2 = [tables objectForKey:@"os2"]; + NSNumber* ySuperscriptYOffset = [os2 objectForKey:@"ySuperscriptYOffset"]; + if (ySuperscriptYOffset) { + double superOffset = [ySuperscriptYOffset doubleValue]; + baselineShift -= fontSize * superOffset / [unitsPerEm doubleValue]; + } + } else if ([baselineShiftString isEqualToString:@"baseline"]) { + } else { + baselineShift -= [PropHelper fromRelativeWithNSString:baselineShiftString + relative:fontSize + offset:0 + scale:1 + fontSize:fontSize]; + } + break; + } + } + } + int i = -1; for(CFIndex g = 0; g < runGlyphCount; g++) { - CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[g], nil); + i++; + bool alreadyRenderedGraphemeCluster = ligature[i]; /* Determine the glyph's charwidth (i.e., the amount which the current text position advances horizontally when the glyph is drawn using horizontal text layout). */ - CGFloat charWidth = glyph_advances[g].width * scaleSpacingAndGlyphs; - //CGPoint glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))]; + double unkernedAdvance = CTFontGetAdvancesForGlyphs(fontRef, kCTFontOrientationHorizontal, glyphs + g, NULL, 1); + CGFloat charWidth = unkernedAdvance * scaleSpacingAndGlyphs; /* For each subsequent glyph, set a new startpoint-on-the-path as the previous @@ -702,8 +692,8 @@ A negative value is an error (see Error processing). using the user agent's distance along the path algorithm. */ if (autoKerning) { - // TODO double kerned = advances[index] * scaleSpacingAndGlyphs; - //kerning = kerned - charWidth; + double kerned = advances[g].width * scaleSpacingAndGlyphs; + kerning = kerned - charWidth; } bool isWordSeparator = false; @@ -711,12 +701,34 @@ A negative value is an error (see Error processing). double spacing = wordSpace + letterSpacing; double advance = charWidth + spacing; - double x = [gc nextXWithDouble:charWidth]; + double x = [gc nextXWithDouble:kerning + charWidth]; double y = [gc nextY]; double dx = [gc nextDeltaX]; double dy = [gc nextDeltaY]; double r = [[gc nextRotation] doubleValue] / radToDeg; + if (alreadyRenderedGraphemeCluster) { + // Skip rendering other grapheme clusters of ligatures (already rendered), + // But, make sure to increment index positions by making gc.next() calls. + continue; + } + + int len = 2; + int nextIndex = i; + CGGlyph glyph = glyphs[g]; + bool hasLigature = false; + while (++nextIndex < n) { + NSString* nextLigature = [str substringWithRange:NSMakeRange(i, len++)]; + bool hasNextLigature = hasGlyph(fontRef, nextLigature, &glyph, allowOptionalLigatures); + if (hasNextLigature) { + ligature[nextIndex] = true; + hasLigature = true; + } else { + break; + } + } + CGPathRef glyphPath = CTFontCreatePathForGlyph(runFont, glyph, nil); + advance *= side; charWidth *= side; double cursor = offset + (x + dx) * side; @@ -759,13 +771,13 @@ A negative value is an error (see Error processing). transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-halfWay, dy + baselineShift), transform); transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, y), transform); } else { - transform = CGAffineTransformMakeTranslation(startPoint, y + dy); + transform = CGAffineTransformMakeTranslation(startPoint, y + dy + baselineShift); transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(r), transform); } transform = CGAffineTransformScale(transform, 1.0, -1.0); - CGPathAddPath(path, &transform, letter); - CGPathRelease(letter); + CGPathAddPath(path, &transform, glyphPath); + CGPathRelease(glyphPath); } } @@ -794,15 +806,14 @@ - (void)setupTextPath:(CGContextRef)context _path = nil; textPath = nil; textPathPath = nil; - //_bezierTransformer = nil; [self traverseTextSuperviews:^(__kindof RNSVGText *node) { if ([node class] == [RNSVGTextPath class]) { textPath = (RNSVGTextPath*) node; - //_bezierTransformer = [textPath getBezierTransformer]; textPathPath = [textPath getPath]; _path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; _path = [_path bezierPathByFlatteningPathAndImmutable:YES]; pathLength = [_path length]; + isClosed = [_path isClosed]; return NO; } return YES; @@ -825,14 +836,44 @@ - (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block } } -- (BOOL)textPathHasReachedEnd +bool hasGlyph(CTFontRef fontRef, NSString * str, CGGlyph* glyph, bool allowOptionalLigatures) { - return [_bezierTransformer hasReachedEnd]; -} + CFStringRef string = (__bridge CFStringRef)str; + CFDictionaryRef attributes; + NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1]; -- (BOOL)textPathHasReachedStart -{ - return [_bezierTransformer hasReachedStart]; + if (fontRef != nil) { + attributes = (__bridge CFDictionaryRef)@{ + (NSString *)kCTFontAttributeName: (__bridge id)fontRef, + (NSString *)NSLigatureAttributeName: lig + }; + } else { + attributes = (__bridge CFDictionaryRef)@{ + (NSString *)NSLigatureAttributeName: lig + }; + } + + CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); + CTLineRef line = CTLineCreateWithAttributedString(attrString); + CFArrayRef runs = CTLineGetGlyphRuns(line); + + CFIndex runEnd = CFArrayGetCount(runs); + if (runEnd > 1) { + return false; + } + CTRunRef run = CFArrayGetValueAtIndex(runs, 0); + CFIndex runGlyphCount = CTRunGetGlyphCount(run); + + CGGlyph glyphs[runGlyphCount]; + CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); + + bool hasGlyph = runGlyphCount == 1; + + if (hasGlyph) { + *glyph = glyphs[0]; + } + + return hasGlyph; } @end diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index b977fc40a..e0d3902cb 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -25,6 +25,5 @@ - (void)releaseCachedPath; - (CGPathRef)getGroupPath:(CGContextRef)context; - (CTFontRef)getFontFromContext; -- (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth; @end diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 328e617b7..78e7495a6 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -10,14 +10,12 @@ #import "RNSVGTextPath.h" #import #import -#import "RNSVGGlyphContext.h" #import "GlyphContext.h" @implementation RNSVGText { RNSVGText *_textRoot; GlyphContext *_glyphContext; - RNSVGGlyphContext *_RNSVGGlyphContext; } - (void)renderLayerTo:(CGContextRef)context @@ -40,8 +38,6 @@ - (void)setupGlyphContext:(CGContextRef)context { _glyphContext = [[GlyphContext alloc] initWithScale:1 width:[self getContextWidth] height:[self getContextHeight]]; - _RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] - height:[self getContextHeight]]; } // release the cached CGPathRef for RNSVGTSpan @@ -95,11 +91,6 @@ - (RNSVGText *)getTextRoot return _textRoot; } -- (RNSVGGlyphContext *)getRNSVGGlyphContext -{ - return _RNSVGGlyphContext; -} - - (GlyphContext *)getGlyphContext { return _glyphContext; @@ -107,12 +98,6 @@ - (GlyphContext *)getGlyphContext - (void)pushGlyphContext { - /* - [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font - deltaX:self.deltaX - deltaY:self.deltaY - positionX:self.positionX - positionY:self.positionY];*/ [[[self getTextRoot] getGlyphContext] pushContextwithRNSVGText:self reset:false font:self.font @@ -125,7 +110,6 @@ - (void)pushGlyphContext - (void)popGlyphContext { - //[[[self getTextRoot] getRNSVGGlyphContext] popContext]; [[[self getTextRoot] getGlyphContext] popContext]; } @@ -134,9 +118,4 @@ - (CTFontRef)getFontFromContext return [[[self getTextRoot] getGlyphContext] getGlyphFont]; } -- (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth -{ - return [[[self getTextRoot] getRNSVGGlyphContext] getNextGlyphPoint:(CGPoint)offset glyphWidth:glyphWidth]; -} - @end diff --git a/ios/Text/RNSVGTextPath.h b/ios/Text/RNSVGTextPath.h index 71125673e..ec08b7ada 100644 --- a/ios/Text/RNSVGTextPath.h +++ b/ios/Text/RNSVGTextPath.h @@ -9,7 +9,6 @@ #import #import #import "RNSVGText.h" -#import "RNSVGBezierTransformer.h" @interface RNSVGTextPath : RNSVGText @@ -21,6 +20,5 @@ @property (nonatomic, strong) NSString *startOffset; - (RNSVGPath *)getPath; -- (RNSVGBezierTransformer *)getBezierTransformer; @end diff --git a/ios/Text/RNSVGTextPath.m b/ios/Text/RNSVGTextPath.m index 3f28dcfc8..310a6b0e1 100644 --- a/ios/Text/RNSVGTextPath.m +++ b/ios/Text/RNSVGTextPath.m @@ -8,7 +8,6 @@ #import "RNSVGTextPath.h" -#import "RNSVGBezierTransformer.h" @implementation RNSVGTextPath @@ -31,14 +30,6 @@ - (RNSVGPath *)getPath return path; } -- (RNSVGBezierTransformer *)getBezierTransformer -{ - RNSVGPath *path = [self getPath]; - CGFloat startOffset = [self relativeOnWidth:self.startOffset]; - return [[RNSVGBezierTransformer alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves] - startOffset:startOffset]; -} - - (CGPathRef)getPath:(CGContextRef)context { return [self getGroupPath:context]; diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m index 74cabcf38..fc154993a 100644 --- a/ios/ViewManagers/RNSVGTextManager.m +++ b/ios/ViewManagers/RNSVGTextManager.m @@ -27,5 +27,9 @@ - (RNSVGRenderable *)node RCT_EXPORT_VIEW_PROPERTY(positionY, NSArray) RCT_EXPORT_VIEW_PROPERTY(rotate, NSArray) RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary) +RCT_EXPORT_VIEW_PROPERTY(textLength, NSString) +RCT_EXPORT_VIEW_PROPERTY(baselineShift, NSString) +RCT_EXPORT_VIEW_PROPERTY(lengthAdjust, NSString) +RCT_EXPORT_VIEW_PROPERTY(alignmentBaseline, NSString) @end diff --git a/ios/ViewManagers/RNSVGTextPathManager.m b/ios/ViewManagers/RNSVGTextPathManager.m index 3ff778cd6..b5efb6a71 100644 --- a/ios/ViewManagers/RNSVGTextPathManager.m +++ b/ios/ViewManagers/RNSVGTextPathManager.m @@ -20,6 +20,10 @@ - (RNSVGRenderable *)node } RCT_EXPORT_VIEW_PROPERTY(href, NSString) +RCT_EXPORT_VIEW_PROPERTY(side, NSString) +RCT_EXPORT_VIEW_PROPERTY(method, NSString) +RCT_EXPORT_VIEW_PROPERTY(midLine, NSString) +RCT_EXPORT_VIEW_PROPERTY(spacing, NSString) RCT_EXPORT_VIEW_PROPERTY(startOffset, NSString) @end From 5a01d672b9dc2113aa1677d552bbe2f235173e64 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 29 Aug 2017 03:16:52 +0300 Subject: [PATCH 186/198] Optimize and simplify hasGlyph. --- ios/Text/RNSVGTSpan.m | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 79b519aea..7cbac8ab1 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -671,6 +671,19 @@ A negative value is an error (see Error processing). } } int i = -1; + CFDictionaryRef ligattributes; + NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1]; + + if (fontRef != nil) { + ligattributes = (__bridge CFDictionaryRef)@{ + (NSString *)kCTFontAttributeName: (__bridge id)fontRef, + (NSString *)NSLigatureAttributeName: lig + }; + } else { + ligattributes = (__bridge CFDictionaryRef)@{ + (NSString *)NSLigatureAttributeName: lig + }; + } for(CFIndex g = 0; g < runGlyphCount; g++) { i++; bool alreadyRenderedGraphemeCluster = ligature[i]; @@ -719,7 +732,7 @@ A negative value is an error (see Error processing). bool hasLigature = false; while (++nextIndex < n) { NSString* nextLigature = [str substringWithRange:NSMakeRange(i, len++)]; - bool hasNextLigature = hasGlyph(fontRef, nextLigature, &glyph, allowOptionalLigatures); + bool hasNextLigature = hasGlyph(fontRef, nextLigature, &glyph, ligattributes); if (hasNextLigature) { ligature[nextIndex] = true; hasLigature = true; @@ -836,23 +849,9 @@ - (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block } } -bool hasGlyph(CTFontRef fontRef, NSString * str, CGGlyph* glyph, bool allowOptionalLigatures) +bool hasGlyph(CTFontRef fontRef, NSString * str, CGGlyph* glyph, CFDictionaryRef attributes) { CFStringRef string = (__bridge CFStringRef)str; - CFDictionaryRef attributes; - NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1]; - - if (fontRef != nil) { - attributes = (__bridge CFDictionaryRef)@{ - (NSString *)kCTFontAttributeName: (__bridge id)fontRef, - (NSString *)NSLigatureAttributeName: lig - }; - } else { - attributes = (__bridge CFDictionaryRef)@{ - (NSString *)NSLigatureAttributeName: lig - }; - } - CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CTLineRef line = CTLineCreateWithAttributedString(attrString); CFArrayRef runs = CTLineGetGlyphRuns(line); @@ -863,14 +862,10 @@ bool hasGlyph(CTFontRef fontRef, NSString * str, CGGlyph* glyph, bool allowOptio } CTRunRef run = CFArrayGetValueAtIndex(runs, 0); CFIndex runGlyphCount = CTRunGetGlyphCount(run); - - CGGlyph glyphs[runGlyphCount]; - CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); - bool hasGlyph = runGlyphCount == 1; if (hasGlyph) { - *glyph = glyphs[0]; + CTRunGetGlyphs(run, CFRangeMake(0, 1), glyph); } return hasGlyph; From 7f88436332bd78bac2dfc57eb509d846cde922d6 Mon Sep 17 00:00:00 2001 From: magicismight Date: Sun, 24 Sep 2017 15:59:18 +0800 Subject: [PATCH 187/198] Remove unused code --- lib/extract/extractTransform.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js index 7f1b23292..bc2214fa9 100644 --- a/lib/extract/extractTransform.js +++ b/lib/extract/extractTransform.js @@ -13,29 +13,15 @@ function transformToMatrix(props, transform) { return pooledMatrix.toArray(); } +const SPLIT_REGEX = /[\s*()|,]/; + class TransformParser { - constructor() { - const floating = '(\\-?[\\d\\.e]+)'; - const commaSpace = '\\,?\\s*'; - - this.regex = { - split: /[\s*()|,]/, - matrix: new RegExp( - '^matrix\\(' + - floating + commaSpace + - floating + commaSpace + - floating + commaSpace + - floating + commaSpace + - floating + commaSpace + - floating + '\\)$') - }; - } parse(transform) { if (transform) { const retval = {}; let transLst = _.filter( - transform.split(this.regex.split), + transform.split(SPLIT_REGEX), (ele) => { return ele !== ''; } From d5923bb4fe175d941517bf0e59fbc036c9e9fe1f Mon Sep 17 00:00:00 2001 From: magicismight Date: Sun, 24 Sep 2017 21:19:27 +0800 Subject: [PATCH 188/198] fix exc_bad_access errors --- ios/RNSVGRenderable.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index a66ea0b0f..a3f4a371a 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -21,12 +21,12 @@ @property (nonatomic, assign) RNSVGCGFCRule fillRule; @property (nonatomic, strong) RNSVGBrush *stroke; @property (nonatomic, assign) CGFloat strokeOpacity; -@property (nonatomic, assign) NSString *strokeWidth; +@property (nonatomic, strong) NSString *strokeWidth; @property (nonatomic, assign) CGLineCap strokeLinecap; @property (nonatomic, assign) CGLineJoin strokeLinejoin; @property (nonatomic, assign) CGFloat strokeMiterlimit; @property (nonatomic, assign) RNSVGCGFloatArray strokeDasharrayData; -@property (nonatomic, assign) NSArray *strokeDasharray; +@property (nonatomic, strong) NSArray *strokeDasharray; @property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, copy) NSArray *propList; From be8797be5239085d5d09eec90a926ae8da7a8583 Mon Sep 17 00:00:00 2001 From: magicismight Date: Sun, 24 Sep 2017 23:44:17 +0800 Subject: [PATCH 189/198] Add PerformanceBezier as a submodule --- .gitmodules | 4 + ios/PerformanceBezier | 1 + ios/PerformanceBezier/.gitignore | 26 - ios/PerformanceBezier/LICENSE | 319 -------- .../project.pbxproj | 724 ------------------ .../PerformanceBezier/Info.plist | 22 - .../PerformanceBezier/JRSwizzle.h | 13 - .../PerformanceBezier/JRSwizzle.m | 134 ---- .../PerformanceBezier-Info.plist | 5 - .../PerformanceBezier/PerformanceBezier.h | 20 - .../PerformanceBezier/UIBezierPath+Center.h | 17 - .../PerformanceBezier/UIBezierPath+Center.m | 18 - .../UIBezierPath+Clockwise.h | 18 - .../UIBezierPath+Clockwise.m | 50 -- .../UIBezierPath+Description.h | 13 - .../UIBezierPath+Description.m | 57 -- .../PerformanceBezier/UIBezierPath+Equals.h | 18 - .../PerformanceBezier/UIBezierPath+Equals.m | 18 - .../UIBezierPath+FirstLast.h | 21 - .../UIBezierPath+FirstLast.m | 61 -- .../PerformanceBezier/UIBezierPath+NSOSX.h | 54 -- .../PerformanceBezier/UIBezierPath+NSOSX.m | 522 ------------- .../UIBezierPath+NSOSX_Private.h | 29 - .../UIBezierPath+Performance.h | 49 -- .../UIBezierPath+Performance.m | 525 ------------- .../UIBezierPath+Performance_Private.h | 26 - .../PerformanceBezier/UIBezierPath+Trim.h | 35 - .../PerformanceBezier/UIBezierPath+Trim.m | 209 ----- .../PerformanceBezier/UIBezierPath+Trimming.h | 33 - .../PerformanceBezier/UIBezierPath+Trimming.m | 365 --------- .../PerformanceBezier/UIBezierPath+Uncached.h | 17 - .../PerformanceBezier/UIBezierPath+Uncached.m | 23 - .../PerformanceBezier/UIBezierPath+Util.h | 48 -- .../PerformanceBezier/UIBezierPath+Util.m | 316 -------- .../UIBezierPathProperties.h | 25 - .../UIBezierPathProperties.m | 81 -- .../PerformanceBezierTests/Info.plist | 24 - .../PerformanceBezierAbstractTest.h | 23 - .../PerformanceBezierAbstractTest.m | 161 ---- .../PerformanceBezierClockwiseTests.m | 189 ----- ios/PerformanceBezier/README.md | 67 -- ios/PerformanceBezier/SubdivideLicense | 32 - ios/Text/RNSVGTSpan.m | 2 +- package.json | 2 +- 44 files changed, 7 insertions(+), 4409 deletions(-) create mode 160000 ios/PerformanceBezier delete mode 100644 ios/PerformanceBezier/.gitignore delete mode 100644 ios/PerformanceBezier/LICENSE delete mode 100644 ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj delete mode 100644 ios/PerformanceBezier/PerformanceBezier/Info.plist delete mode 100644 ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist delete mode 100644 ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h delete mode 100644 ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m delete mode 100644 ios/PerformanceBezier/PerformanceBezierTests/Info.plist delete mode 100644 ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h delete mode 100644 ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m delete mode 100644 ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m delete mode 100644 ios/PerformanceBezier/README.md delete mode 100644 ios/PerformanceBezier/SubdivideLicense diff --git a/.gitmodules b/.gitmodules index 3c3d57e09..120d2ae80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "Example"] path = Example url = https://github.com/magicismight/react-native-svg-example.git + +[submodule "ios/PerformanceBezier"] + path = ios/PerformanceBezier + url = https://github.com/adamwulf/PerformanceBezier.git diff --git a/ios/PerformanceBezier b/ios/PerformanceBezier new file mode 160000 index 000000000..0e0ad4b03 --- /dev/null +++ b/ios/PerformanceBezier @@ -0,0 +1 @@ +Subproject commit 0e0ad4b0303ec80c3c32257addfef9ef6e2a2eb7 diff --git a/ios/PerformanceBezier/.gitignore b/ios/PerformanceBezier/.gitignore deleted file mode 100644 index a3cd143a4..000000000 --- a/ios/PerformanceBezier/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control -# -# Pods/ diff --git a/ios/PerformanceBezier/LICENSE b/ios/PerformanceBezier/LICENSE deleted file mode 100644 index a7fe54e41..000000000 --- a/ios/PerformanceBezier/LICENSE +++ /dev/null @@ -1,319 +0,0 @@ -Creative Commons Legal Code - -Attribution 3.0 Unported - -CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE -LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN -ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS -INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES -REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR -DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - -a. "Adaptation" means a work based upon the Work, or upon the Work and -other pre-existing works, such as a translation, adaptation, -derivative work, arrangement of music or other alterations of a -literary or artistic work, or phonogram or performance and includes -cinematographic adaptations or any other form in which the Work may be -recast, transformed, or adapted including in any form recognizably -derived from the original, except that a work that constitutes a -Collection will not be considered an Adaptation for the purpose of -this License. For the avoidance of doubt, where the Work is a musical -work, performance or phonogram, the synchronization of the Work in -timed-relation with a moving image ("synching") will be considered an -Adaptation for the purpose of this License. -b. "Collection" means a collection of literary or artistic works, such as -encyclopedias and anthologies, or performances, phonograms or -broadcasts, or other works or subject matter other than works listed -in Section 1(f) below, which, by reason of the selection and -arrangement of their contents, constitute intellectual creations, in -which the Work is included in its entirety in unmodified form along -with one or more other contributions, each constituting separate and -independent works in themselves, which together are assembled into a -collective whole. A work that constitutes a Collection will not be -considered an Adaptation (as defined above) for the purposes of this -License. -c. "Distribute" means to make available to the public the original and -copies of the Work or Adaptation, as appropriate, through sale or -other transfer of ownership. -d. "Licensor" means the individual, individuals, entity or entities that -offer(s) the Work under the terms of this License. -e. "Original Author" means, in the case of a literary or artistic work, -the individual, individuals, entity or entities who created the Work -or if no individual or entity can be identified, the publisher; and in -addition (i) in the case of a performance the actors, singers, -musicians, dancers, and other persons who act, sing, deliver, declaim, -play in, interpret or otherwise perform literary or artistic works or -expressions of folklore; (ii) in the case of a phonogram the producer -being the person or legal entity who first fixes the sounds of a -performance or other sounds; and, (iii) in the case of broadcasts, the -organization that transmits the broadcast. -f. "Work" means the literary and/or artistic work offered under the terms -of this License including without limitation any production in the -literary, scientific and artistic domain, whatever may be the mode or -form of its expression including digital form, such as a book, -pamphlet and other writing; a lecture, address, sermon or other work -of the same nature; a dramatic or dramatico-musical work; a -choreographic work or entertainment in dumb show; a musical -composition with or without words; a cinematographic work to which are -assimilated works expressed by a process analogous to cinematography; -a work of drawing, painting, architecture, sculpture, engraving or -lithography; a photographic work to which are assimilated works -expressed by a process analogous to photography; a work of applied -art; an illustration, map, plan, sketch or three-dimensional work -relative to geography, topography, architecture or science; a -performance; a broadcast; a phonogram; a compilation of data to the -extent it is protected as a copyrightable work; or a work performed by -a variety or circus performer to the extent it is not otherwise -considered a literary or artistic work. -g. "You" means an individual or entity exercising rights under this -License who has not previously violated the terms of this License with -respect to the Work, or who has received express permission from the -Licensor to exercise rights under this License despite a previous -violation. -h. "Publicly Perform" means to perform public recitations of the Work and -to communicate to the public those public recitations, by any means or -process, including by wire or wireless means or public digital -performances; to make available to the public Works in such a way that -members of the public may access these Works from a place and at a -place individually chosen by them; to perform the Work to the public -by any means or process and the communication to the public of the -performances of the Work, including by public digital performance; to -broadcast and rebroadcast the Work by any means including signs, -sounds or images. -i. "Reproduce" means to make copies of the Work by any means including -without limitation by sound or visual recordings and the right of -fixation and reproducing fixations of the Work, including storage of a -protected performance or phonogram in digital form or other electronic -medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - -a. to Reproduce the Work, to incorporate the Work into one or more -Collections, and to Reproduce the Work as incorporated in the -Collections; -b. to create and Reproduce Adaptations provided that any such Adaptation, -including any translation in any medium, takes reasonable steps to -clearly label, demarcate or otherwise identify that changes were made -to the original Work. For example, a translation could be marked "The -original work was translated from English to Spanish," or a -modification could indicate "The original work has been modified."; -c. to Distribute and Publicly Perform the Work including as incorporated -in Collections; and, -d. to Distribute and Publicly Perform Adaptations. -e. For the avoidance of doubt: - -i. Non-waivable Compulsory License Schemes. In those jurisdictions in -which the right to collect royalties through any statutory or -compulsory licensing scheme cannot be waived, the Licensor -reserves the exclusive right to collect such royalties for any -exercise by You of the rights granted under this License; -ii. Waivable Compulsory License Schemes. In those jurisdictions in -which the right to collect royalties through any statutory or -compulsory licensing scheme can be waived, the Licensor waives the -exclusive right to collect such royalties for any exercise by You -of the rights granted under this License; and, -iii. Voluntary License Schemes. The Licensor waives the right to -collect royalties, whether individually or, in the event that the -Licensor is a member of a collecting society that administers -voluntary licensing schemes, via that society, from any exercise -by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - -a. You may Distribute or Publicly Perform the Work only under the terms -of this License. You must include a copy of, or the Uniform Resource -Identifier (URI) for, this License with every copy of the Work You -Distribute or Publicly Perform. You may not offer or impose any terms -on the Work that restrict the terms of this License or the ability of -the recipient of the Work to exercise the rights granted to that -recipient under the terms of the License. You may not sublicense the -Work. You must keep intact all notices that refer to this License and -to the disclaimer of warranties with every copy of the Work You -Distribute or Publicly Perform. When You Distribute or Publicly -Perform the Work, You may not impose any effective technological -measures on the Work that restrict the ability of a recipient of the -Work from You to exercise the rights granted to that recipient under -the terms of the License. This Section 4(a) applies to the Work as -incorporated in a Collection, but this does not require the Collection -apart from the Work itself to be made subject to the terms of this -License. If You create a Collection, upon notice from any Licensor You -must, to the extent practicable, remove from the Collection any credit -as required by Section 4(b), as requested. If You create an -Adaptation, upon notice from any Licensor You must, to the extent -practicable, remove from the Adaptation any credit as required by -Section 4(b), as requested. -b. If You Distribute, or Publicly Perform the Work or any Adaptations or -Collections, You must, unless a request has been made pursuant to -Section 4(a), keep intact all copyright notices for the Work and -provide, reasonable to the medium or means You are utilizing: (i) the -name of the Original Author (or pseudonym, if applicable) if supplied, -and/or if the Original Author and/or Licensor designate another party -or parties (e.g., a sponsor institute, publishing entity, journal) for -attribution ("Attribution Parties") in Licensor's copyright notice, -terms of service or by other reasonable means, the name of such party -or parties; (ii) the title of the Work if supplied; (iii) to the -extent reasonably practicable, the URI, if any, that Licensor -specifies to be associated with the Work, unless such URI does not -refer to the copyright notice or licensing information for the Work; -and (iv) , consistent with Section 3(b), in the case of an Adaptation, -a credit identifying the use of the Work in the Adaptation (e.g., -"French translation of the Work by Original Author," or "Screenplay -based on original Work by Original Author"). The credit required by -this Section 4 (b) may be implemented in any reasonable manner; -provided, however, that in the case of a Adaptation or Collection, at -a minimum such credit will appear, if a credit for all contributing -authors of the Adaptation or Collection appears, then as part of these -credits and in a manner at least as prominent as the credits for the -other contributing authors. For the avoidance of doubt, You may only -use the credit required by this Section for the purpose of attribution -in the manner set out above and, by exercising Your rights under this -License, You may not implicitly or explicitly assert or imply any -connection with, sponsorship or endorsement by the Original Author, -Licensor and/or Attribution Parties, as appropriate, of You or Your -use of the Work, without the separate, express prior written -permission of the Original Author, Licensor and/or Attribution -Parties. -c. Except as otherwise agreed in writing by the Licensor or as may be -otherwise permitted by applicable law, if You Reproduce, Distribute or -Publicly Perform the Work either by itself or as part of any -Adaptations or Collections, You must not distort, mutilate, modify or -take other derogatory action in relation to the Work which would be -prejudicial to the Original Author's honor or reputation. Licensor -agrees that in those jurisdictions (e.g. Japan), in which any exercise -of the right granted in Section 3(b) of this License (the right to -make Adaptations) would be deemed to be a distortion, mutilation, -modification or other derogatory action prejudicial to the Original -Author's honor and reputation, the Licensor will waive or not assert, -as appropriate, this Section, to the fullest extent permitted by the -applicable national law, to enable You to reasonably exercise Your -right under Section 3(b) of this License (right to make Adaptations) -but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - -a. This License and the rights granted hereunder will terminate -automatically upon any breach by You of the terms of this License. -Individuals or entities who have received Adaptations or Collections -from You under this License, however, will not have their licenses -terminated provided such individuals or entities remain in full -compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will -survive any termination of this License. -b. Subject to the above terms and conditions, the license granted here is -perpetual (for the duration of the applicable copyright in the Work). -Notwithstanding the above, Licensor reserves the right to release the -Work under different license terms or to stop distributing the Work at -any time; provided, however that any such election will not serve to -withdraw this License (or any other license that has been, or is -required to be, granted under the terms of this License), and this -License will continue in full force and effect unless terminated as -stated above. - -8. Miscellaneous - -a. Each time You Distribute or Publicly Perform the Work or a Collection, -the Licensor offers to the recipient a license to the Work on the same -terms and conditions as the license granted to You under this License. -b. Each time You Distribute or Publicly Perform an Adaptation, Licensor -offers to the recipient a license to the original Work on the same -terms and conditions as the license granted to You under this License. -c. If any provision of this License is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this License, and without further action -by the parties to this agreement, such provision shall be reformed to -the minimum extent necessary to make such provision valid and -enforceable. -d. No term or provision of this License shall be deemed waived and no -breach consented to unless such waiver or consent shall be in writing -and signed by the party to be charged with such waiver or consent. -e. This License constitutes the entire agreement between the parties with -respect to the Work licensed here. There are no understandings, -agreements or representations with respect to the Work not specified -here. Licensor shall not be bound by any additional provisions that -may appear in any communication from You. This License may not be -modified without the mutual written agreement of the Licensor and You. -f. The rights granted under, and the subject matter referenced, in this -License were drafted utilizing the terminology of the Berne Convention -for the Protection of Literary and Artistic Works (as amended on -September 28, 1979), the Rome Convention of 1961, the WIPO Copyright -Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 -and the Universal Copyright Convention (as revised on July 24, 1971). -These rights and subject matter take effect in the relevant -jurisdiction in which the License terms are sought to be enforced -according to the corresponding provisions of the implementation of -those treaty provisions in the applicable national law. If the -standard suite of rights granted under applicable copyright law -includes additional rights not granted under this License, such -additional rights are deemed to be included in the License; this -License is not intended to restrict the license of any rights under -applicable law. - - -Creative Commons Notice - -Creative Commons is not a party to this License, and makes no warranty -whatsoever in connection with the Work. Creative Commons will not be -liable to You or any party on any legal theory for any damages -whatsoever, including without limitation any general, special, -incidental or consequential damages arising in connection to this -license. Notwithstanding the foregoing two (2) sentences, if Creative -Commons has expressly identified itself as the Licensor hereunder, it -shall have all rights and obligations of Licensor. - -Except for the limited purpose of indicating to the public that the -Work is licensed under the CCPL, Creative Commons does not authorize -the use by either party of the trademark "Creative Commons" or any -related trademark or logo of Creative Commons without the prior -written consent of Creative Commons. Any permitted use will be in -compliance with Creative Commons' then-current trademark usage -guidelines, as may be published on its website or otherwise made -available upon request from time to time. For the avoidance of doubt, -this trademark restriction does not form part of this License. - -Creative Commons may be contacted at https://creativecommons.org/. \ No newline at end of file diff --git a/ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj b/ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj deleted file mode 100644 index 1148c4849..000000000 --- a/ios/PerformanceBezier/PerformanceBezier.xcodeproj/project.pbxproj +++ /dev/null @@ -1,724 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 6617530D1A8DC45A0051D5CB /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */; }; - 66AB566C1B0D9830005E6FB1 /* UIBezierPath+Util.h in Headers */ = {isa = PBXBuildFile; fileRef = 66AB566A1B0D9830005E6FB1 /* UIBezierPath+Util.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66AB566D1B0D9830005E6FB1 /* UIBezierPath+Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 66AB566B1B0D9830005E6FB1 /* UIBezierPath+Util.m */; }; - 66B9D2A61A8D609200CAC341 /* PerformanceBezierAbstractTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 66D4021F1A7EDACA00043802 /* PerformanceBezierAbstractTest.m */; }; - 66B9D2A71A8D609300CAC341 /* PerformanceBezierClockwiseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 66D4021C1A7EDA9000043802 /* PerformanceBezierClockwiseTests.m */; }; - 66D1B7971AFEAC6F00210262 /* UIBezierPath+Trim.h in Headers */ = {isa = PBXBuildFile; fileRef = 66D1B7951AFEAC6F00210262 /* UIBezierPath+Trim.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66D1B7981AFEAC6F00210262 /* UIBezierPath+Trim.m in Sources */ = {isa = PBXBuildFile; fileRef = 66D1B7961AFEAC6F00210262 /* UIBezierPath+Trim.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 66F2EBE41A8DC05100D536E9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6682FF251A8DBACF00187325 /* Foundation.framework */; }; - 66F2EBF61A8DC12600D536E9 /* UIBezierPath+Center.m in Sources */ = {isa = PBXBuildFile; fileRef = 660E0FD81A80055300F19D8A /* UIBezierPath+Center.m */; }; - 66F2EBF71A8DC12800D536E9 /* UIBezierPath+Clockwise.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617D01A7EB88200C0B447 /* UIBezierPath+Clockwise.m */; }; - 66F2EBF81A8DC13100D536E9 /* UIBezierPath+Equals.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617CC1A7EB86200C0B447 /* UIBezierPath+Equals.m */; }; - 66F2EBF91A8DC13300D536E9 /* UIBezierPath+Description.m in Sources */ = {isa = PBXBuildFile; fileRef = 663355CC1A8575F600C45718 /* UIBezierPath+Description.m */; }; - 66F2EBFA1A8DC13500D536E9 /* UIBezierPath+NSOSX.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617C11A7EB79F00C0B447 /* UIBezierPath+NSOSX.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 66F2EBFB1A8DC13700D536E9 /* UIBezierPath+Performance.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617F61A7EC58700C0B447 /* UIBezierPath+Performance.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 66F2EBFC1A8DC13900D536E9 /* UIBezierPath+Uncached.m in Sources */ = {isa = PBXBuildFile; fileRef = 663355D61A857A2700C45718 /* UIBezierPath+Uncached.m */; }; - 66F2EBFD1A8DC13D00D536E9 /* UIBezierPath+FirstLast.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617FB1A7EC5D400C0B447 /* UIBezierPath+FirstLast.m */; }; - 66F2EBFE1A8DC13F00D536E9 /* UIBezierPathProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617B81A7EB73A00C0B447 /* UIBezierPathProperties.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 66F2EBFF1A8DC14100D536E9 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 667617D41A7EB9AC00C0B447 /* JRSwizzle.m */; }; - 66F2EC001A8DC15600D536E9 /* UIBezierPath+Uncached.h in Headers */ = {isa = PBXBuildFile; fileRef = 663355D51A857A2700C45718 /* UIBezierPath+Uncached.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC011A8DC15800D536E9 /* UIBezierPath+Performance.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617F41A7EC58700C0B447 /* UIBezierPath+Performance.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC021A8DC15A00D536E9 /* UIBezierPath+NSOSX.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617C01A7EB79F00C0B447 /* UIBezierPath+NSOSX.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC031A8DC15B00D536E9 /* UIBezierPath+Description.h in Headers */ = {isa = PBXBuildFile; fileRef = 663355CB1A8575F600C45718 /* UIBezierPath+Description.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC041A8DC15E00D536E9 /* UIBezierPath+Equals.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617CB1A7EB86200C0B447 /* UIBezierPath+Equals.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC051A8DC16000D536E9 /* UIBezierPath+Clockwise.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617CF1A7EB88200C0B447 /* UIBezierPath+Clockwise.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC061A8DC16100D536E9 /* UIBezierPath+Center.h in Headers */ = {isa = PBXBuildFile; fileRef = 660E0FD71A80055300F19D8A /* UIBezierPath+Center.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC071A8DC16300D536E9 /* UIBezierPath+Performance_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617F51A7EC58700C0B447 /* UIBezierPath+Performance_Private.h */; }; - 66F2EC081A8DC16500D536E9 /* UIBezierPath+NSOSX_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617BF1A7EB79F00C0B447 /* UIBezierPath+NSOSX_Private.h */; }; - 66F2EC091A8DC16600D536E9 /* UIBezierPath+FirstLast.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617FA1A7EC5D400C0B447 /* UIBezierPath+FirstLast.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC0A1A8DC16800D536E9 /* UIBezierPathProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617B71A7EB73A00C0B447 /* UIBezierPathProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC0B1A8DC16900D536E9 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617D31A7EB9AC00C0B447 /* JRSwizzle.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC0C1A8DC16B00D536E9 /* PerformanceBezier.h in Headers */ = {isa = PBXBuildFile; fileRef = 667617AF1A7EB5CB00C0B447 /* PerformanceBezier.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66F2EC0D1A8DC22000D536E9 /* PerformanceBezier-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 66F2EBEA1A8DC05100D536E9 /* PerformanceBezier-Info.plist */; }; - 9494C4A01F4715A000D5BCFD /* UIBezierPath+Trimming.h in Headers */ = {isa = PBXBuildFile; fileRef = 9494C49E1F4715A000D5BCFD /* UIBezierPath+Trimming.h */; }; - 9494C4A11F4715A000D5BCFD /* UIBezierPath+Trimming.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C49F1F4715A000D5BCFD /* UIBezierPath+Trimming.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 6617530E1A8DC8300051D5CB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 661B30C91A7EB43A008549C7 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 66F2EBE21A8DC05100D536E9; - remoteInfo = PerformanceBezier; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 660E0FD71A80055300F19D8A /* UIBezierPath+Center.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Center.h"; sourceTree = ""; }; - 660E0FD81A80055300F19D8A /* UIBezierPath+Center.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Center.m"; sourceTree = ""; }; - 660E0FDB1A80226100F19D8A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 660E0FDC1A80284C00F19D8A /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - 663355CB1A8575F600C45718 /* UIBezierPath+Description.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Description.h"; sourceTree = ""; }; - 663355CC1A8575F600C45718 /* UIBezierPath+Description.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Description.m"; sourceTree = ""; }; - 663355D51A857A2700C45718 /* UIBezierPath+Uncached.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Uncached.h"; sourceTree = ""; }; - 663355D61A857A2700C45718 /* UIBezierPath+Uncached.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Uncached.m"; sourceTree = ""; }; - 667617AF1A7EB5CB00C0B447 /* PerformanceBezier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformanceBezier.h; sourceTree = ""; }; - 667617B71A7EB73A00C0B447 /* UIBezierPathProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIBezierPathProperties.h; sourceTree = ""; }; - 667617B81A7EB73A00C0B447 /* UIBezierPathProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIBezierPathProperties.m; sourceTree = ""; }; - 667617BF1A7EB79F00C0B447 /* UIBezierPath+NSOSX_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+NSOSX_Private.h"; sourceTree = ""; }; - 667617C01A7EB79F00C0B447 /* UIBezierPath+NSOSX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+NSOSX.h"; sourceTree = ""; }; - 667617C11A7EB79F00C0B447 /* UIBezierPath+NSOSX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+NSOSX.m"; sourceTree = ""; }; - 667617CB1A7EB86200C0B447 /* UIBezierPath+Equals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Equals.h"; sourceTree = ""; }; - 667617CC1A7EB86200C0B447 /* UIBezierPath+Equals.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Equals.m"; sourceTree = ""; }; - 667617CF1A7EB88200C0B447 /* UIBezierPath+Clockwise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Clockwise.h"; sourceTree = ""; }; - 667617D01A7EB88200C0B447 /* UIBezierPath+Clockwise.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Clockwise.m"; sourceTree = ""; }; - 667617D31A7EB9AC00C0B447 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = ""; }; - 667617D41A7EB9AC00C0B447 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = ""; }; - 667617F41A7EC58700C0B447 /* UIBezierPath+Performance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Performance.h"; sourceTree = ""; }; - 667617F51A7EC58700C0B447 /* UIBezierPath+Performance_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Performance_Private.h"; sourceTree = ""; }; - 667617F61A7EC58700C0B447 /* UIBezierPath+Performance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Performance.m"; sourceTree = ""; }; - 667617FA1A7EC5D400C0B447 /* UIBezierPath+FirstLast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+FirstLast.h"; sourceTree = ""; }; - 667617FB1A7EC5D400C0B447 /* UIBezierPath+FirstLast.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+FirstLast.m"; sourceTree = ""; }; - 66767CC71AFEE4BB00443B03 /* SubdivideLicense */ = {isa = PBXFileReference; lastKnownFileType = text; path = SubdivideLicense; sourceTree = ""; }; - 6682FF251A8DBACF00187325 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; - 66AB566A1B0D9830005E6FB1 /* UIBezierPath+Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Util.h"; sourceTree = ""; }; - 66AB566B1B0D9830005E6FB1 /* UIBezierPath+Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Util.m"; sourceTree = ""; }; - 66B9D28C1A8D5FDE00CAC341 /* PerformanceBezierTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerformanceBezierTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 66B9D2921A8D5FDF00CAC341 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 66D1B7951AFEAC6F00210262 /* UIBezierPath+Trim.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Trim.h"; sourceTree = ""; }; - 66D1B7961AFEAC6F00210262 /* UIBezierPath+Trim.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Trim.m"; sourceTree = ""; }; - 66D4021C1A7EDA9000043802 /* PerformanceBezierClockwiseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceBezierClockwiseTests.m; sourceTree = ""; }; - 66D4021E1A7EDACA00043802 /* PerformanceBezierAbstractTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PerformanceBezierAbstractTest.h; sourceTree = ""; }; - 66D4021F1A7EDACA00043802 /* PerformanceBezierAbstractTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceBezierAbstractTest.m; sourceTree = ""; }; - 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework.static; includeInIndex = 0; path = PerformanceBezier.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 66F2EBE71A8DC05100D536E9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 66F2EBEA1A8DC05100D536E9 /* PerformanceBezier-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PerformanceBezier-Info.plist"; sourceTree = ""; }; - 9494C49E1F4715A000D5BCFD /* UIBezierPath+Trimming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+Trimming.h"; sourceTree = ""; }; - 9494C49F1F4715A000D5BCFD /* UIBezierPath+Trimming.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+Trimming.m"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 66B9D2891A8D5FDE00CAC341 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6617530D1A8DC45A0051D5CB /* PerformanceBezier.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 66F2EBDE1A8DC05100D536E9 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 66F2EBE41A8DC05100D536E9 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 661B30C81A7EB43A008549C7 = { - isa = PBXGroup; - children = ( - 660E0FDB1A80226100F19D8A /* README.md */, - 66767CC71AFEE4BB00443B03 /* SubdivideLicense */, - 660E0FDC1A80284C00F19D8A /* LICENSE */, - 66B9D2831A8D5FDE00CAC341 /* PerformanceBezier */, - 66B9D2901A8D5FDF00CAC341 /* PerformanceBezierTests */, - 66F2EBE51A8DC05100D536E9 /* Supporting Files */, - 6682FF241A8DBACF00187325 /* Frameworks */, - 667617871A7EB50A00C0B447 /* Products */, - ); - sourceTree = ""; - }; - 667617871A7EB50A00C0B447 /* Products */ = { - isa = PBXGroup; - children = ( - 66B9D28C1A8D5FDE00CAC341 /* PerformanceBezierTests.xctest */, - 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */, - ); - name = Products; - sourceTree = ""; - }; - 667617B61A7EB72F00C0B447 /* Categories */ = { - isa = PBXGroup; - children = ( - 9494C49E1F4715A000D5BCFD /* UIBezierPath+Trimming.h */, - 9494C49F1F4715A000D5BCFD /* UIBezierPath+Trimming.m */, - 660E0FD71A80055300F19D8A /* UIBezierPath+Center.h */, - 660E0FD81A80055300F19D8A /* UIBezierPath+Center.m */, - 667617CF1A7EB88200C0B447 /* UIBezierPath+Clockwise.h */, - 667617D01A7EB88200C0B447 /* UIBezierPath+Clockwise.m */, - 667617CB1A7EB86200C0B447 /* UIBezierPath+Equals.h */, - 667617CC1A7EB86200C0B447 /* UIBezierPath+Equals.m */, - 663355CB1A8575F600C45718 /* UIBezierPath+Description.h */, - 663355CC1A8575F600C45718 /* UIBezierPath+Description.m */, - 667617C01A7EB79F00C0B447 /* UIBezierPath+NSOSX.h */, - 667617C11A7EB79F00C0B447 /* UIBezierPath+NSOSX.m */, - 667617F41A7EC58700C0B447 /* UIBezierPath+Performance.h */, - 667617F61A7EC58700C0B447 /* UIBezierPath+Performance.m */, - 66D1B7951AFEAC6F00210262 /* UIBezierPath+Trim.h */, - 66D1B7961AFEAC6F00210262 /* UIBezierPath+Trim.m */, - 663355D51A857A2700C45718 /* UIBezierPath+Uncached.h */, - 663355D61A857A2700C45718 /* UIBezierPath+Uncached.m */, - 66AB566A1B0D9830005E6FB1 /* UIBezierPath+Util.h */, - 66AB566B1B0D9830005E6FB1 /* UIBezierPath+Util.m */, - ); - name = Categories; - sourceTree = ""; - }; - 667617DB1A7EB9B000C0B447 /* Protected */ = { - isa = PBXGroup; - children = ( - 667617D31A7EB9AC00C0B447 /* JRSwizzle.h */, - 667617D41A7EB9AC00C0B447 /* JRSwizzle.m */, - 667617B71A7EB73A00C0B447 /* UIBezierPathProperties.h */, - 667617B81A7EB73A00C0B447 /* UIBezierPathProperties.m */, - 667617FA1A7EC5D400C0B447 /* UIBezierPath+FirstLast.h */, - 667617FB1A7EC5D400C0B447 /* UIBezierPath+FirstLast.m */, - 667617BF1A7EB79F00C0B447 /* UIBezierPath+NSOSX_Private.h */, - 667617F51A7EC58700C0B447 /* UIBezierPath+Performance_Private.h */, - ); - name = Protected; - sourceTree = ""; - }; - 6682FF241A8DBACF00187325 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 6682FF251A8DBACF00187325 /* Foundation.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 66B9D2831A8D5FDE00CAC341 /* PerformanceBezier */ = { - isa = PBXGroup; - children = ( - 667617AF1A7EB5CB00C0B447 /* PerformanceBezier.h */, - 667617DB1A7EB9B000C0B447 /* Protected */, - 667617B61A7EB72F00C0B447 /* Categories */, - ); - path = PerformanceBezier; - sourceTree = ""; - }; - 66B9D2901A8D5FDF00CAC341 /* PerformanceBezierTests */ = { - isa = PBXGroup; - children = ( - 66D4021E1A7EDACA00043802 /* PerformanceBezierAbstractTest.h */, - 66D4021F1A7EDACA00043802 /* PerformanceBezierAbstractTest.m */, - 66D4021C1A7EDA9000043802 /* PerformanceBezierClockwiseTests.m */, - 66B9D2911A8D5FDF00CAC341 /* Supporting Files */, - ); - path = PerformanceBezierTests; - sourceTree = ""; - }; - 66B9D2911A8D5FDF00CAC341 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 66B9D2921A8D5FDF00CAC341 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 66F2EBE51A8DC05100D536E9 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 66F2EBEA1A8DC05100D536E9 /* PerformanceBezier-Info.plist */, - 66F2EBE71A8DC05100D536E9 /* Info.plist */, - ); - name = "Supporting Files"; - path = PerformanceBezier; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 66F2EBDF1A8DC05100D536E9 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 66F2EC0C1A8DC16B00D536E9 /* PerformanceBezier.h in Headers */, - 66F2EC001A8DC15600D536E9 /* UIBezierPath+Uncached.h in Headers */, - 66F2EC0B1A8DC16900D536E9 /* JRSwizzle.h in Headers */, - 66F2EC011A8DC15800D536E9 /* UIBezierPath+Performance.h in Headers */, - 66F2EC0A1A8DC16800D536E9 /* UIBezierPathProperties.h in Headers */, - 66F2EC041A8DC15E00D536E9 /* UIBezierPath+Equals.h in Headers */, - 66F2EC061A8DC16100D536E9 /* UIBezierPath+Center.h in Headers */, - 66F2EC091A8DC16600D536E9 /* UIBezierPath+FirstLast.h in Headers */, - 66F2EC051A8DC16000D536E9 /* UIBezierPath+Clockwise.h in Headers */, - 66AB566C1B0D9830005E6FB1 /* UIBezierPath+Util.h in Headers */, - 66D1B7971AFEAC6F00210262 /* UIBezierPath+Trim.h in Headers */, - 9494C4A01F4715A000D5BCFD /* UIBezierPath+Trimming.h in Headers */, - 66F2EC031A8DC15B00D536E9 /* UIBezierPath+Description.h in Headers */, - 66F2EC021A8DC15A00D536E9 /* UIBezierPath+NSOSX.h in Headers */, - 66F2EC071A8DC16300D536E9 /* UIBezierPath+Performance_Private.h in Headers */, - 66F2EC081A8DC16500D536E9 /* UIBezierPath+NSOSX_Private.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 66B9D28B1A8D5FDE00CAC341 /* PerformanceBezierTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 66B9D2961A8D5FDF00CAC341 /* Build configuration list for PBXNativeTarget "PerformanceBezierTests" */; - buildPhases = ( - 66B9D2881A8D5FDE00CAC341 /* Sources */, - 66B9D2891A8D5FDE00CAC341 /* Frameworks */, - 66B9D28A1A8D5FDE00CAC341 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 6617530F1A8DC8300051D5CB /* PBXTargetDependency */, - ); - name = PerformanceBezierTests; - productName = PerformanceBezierTests; - productReference = 66B9D28C1A8D5FDE00CAC341 /* PerformanceBezierTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 66F2EBE21A8DC05100D536E9 /* PerformanceBezier */ = { - isa = PBXNativeTarget; - buildConfigurationList = 66F2EBF01A8DC05100D536E9 /* Build configuration list for PBXNativeTarget "PerformanceBezier" */; - buildPhases = ( - 66F2EBF31A8DC05E00D536E9 /* Prepare Build Script */, - 66F2EBDD1A8DC05100D536E9 /* Sources */, - 66F2EBDE1A8DC05100D536E9 /* Frameworks */, - 66F2EBDF1A8DC05100D536E9 /* Headers */, - 66F2EBE01A8DC05100D536E9 /* Resources */, - 66F2EBF41A8DC0AB00D536E9 /* Process Headers Script */, - 66F2EBF51A8DC0CD00D536E9 /* Build Framework Script */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = PerformanceBezier; - productName = PerformanceBezier; - productReference = 66F2EBE31A8DC05100D536E9 /* PerformanceBezier.framework */; - productType = "com.apple.product-type.framework.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 661B30C91A7EB43A008549C7 /* Project object */ = { - isa = PBXProject; - attributes = { - CLASSPREFIX = MM; - LastUpgradeCheck = 0830; - ORGANIZATIONNAME = "Milestone Made"; - TargetAttributes = { - 66B9D28B1A8D5FDE00CAC341 = { - CreatedOnToolsVersion = 6.1.1; - }; - 66F2EBE21A8DC05100D536E9 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 661B30CC1A7EB43A008549C7 /* Build configuration list for PBXProject "PerformanceBezier" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 661B30C81A7EB43A008549C7; - productRefGroup = 667617871A7EB50A00C0B447 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 66F2EBE21A8DC05100D536E9 /* PerformanceBezier */, - 66B9D28B1A8D5FDE00CAC341 /* PerformanceBezierTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 66B9D28A1A8D5FDE00CAC341 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 66F2EBE01A8DC05100D536E9 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 66F2EC0D1A8DC22000D536E9 /* PerformanceBezier-Info.plist in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 66F2EBF31A8DC05E00D536E9 /* Prepare Build Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Prepare Build Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "set -e\n\nset +u\nif [[ $UFW_MASTER_SCRIPT_RUNNING ]]\nthen\n# Nothing for the slave script to do\nexit 0\nfi\nset -u\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nUFW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nUFW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$UFW_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nUFW_OTHER_PLATFORM=iphonesimulator\nelse\nUFW_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$UFW_SDK_PLATFORM$ ]]\nthen\nUFW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${UFW_OTHER_PLATFORM}\"\nelse\necho \"Could not find $UFW_SDK_PLATFORM in $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\nONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM:-$ONLY_ACTIVE_ARCH}\n\n# Short-circuit if all binaries are up to date\n\nif [[ -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && \\\n[[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n[[ ! \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -nt \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n([[ \"${ONLY_ACTIVE_PLATFORM}\" == \"YES\" ]] || \\\n([[ -f \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && \\\n[[ -f \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n[[ ! \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -nt \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]]\n)\n)\nthen\nexit 0\nfi\n\n\n# Clean other platform if needed\n\nif [[ ! -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && [[ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]]\nthen\necho \"Platform \\\"$UFW_SDK_PLATFORM\\\" was cleaned recently. Cleaning \\\"$UFW_OTHER_PLATFORM\\\" as well\"\necho xcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" clean\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" clean\nfi\n\n\n# Make sure we are building from fresh binaries\n\nrm -rf \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nrm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\n\nif [[ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]]\nthen\nrm -rf \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nrm -rf \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\nfi\n"; - }; - 66F2EBF41A8DC0AB00D536E9 /* Process Headers Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Process Headers Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\nHEADERS_ROOT=$SRCROOT/$PRODUCT_NAME\nFRAMEWORK_HEADERS_DIR=\"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME/Versions/$FRAMEWORK_VERSION/Headers\"\n\n## only header files expected at this point\nPUBLIC_HEADERS=$(find $FRAMEWORK_HEADERS_DIR/. -not -type d 2> /dev/null | sed -e \"s@.*/@@g\")\n\nFIND_OPTS=\"\"\nfor PUBLIC_HEADER in $PUBLIC_HEADERS; do\nif [ -n \"$FIND_OPTS\" ]; then\nFIND_OPTS=\"$FIND_OPTS -o\"\nfi\nFIND_OPTS=\"$FIND_OPTS -name '$PUBLIC_HEADER'\"\ndone\n\nif [ -n \"$FIND_OPTS\" ]; then\nfor ORIG_HEADER in $(eval \"find $HEADERS_ROOT/. $FIND_OPTS\" 2> /dev/null | sed -e \"s@^$HEADERS_ROOT/./@@g\"); do\nPUBLIC_HEADER=$(basename $ORIG_HEADER)\nRELATIVE_PATH=$(dirname $ORIG_HEADER)\nif [ -e $FRAMEWORK_HEADERS_DIR/$PUBLIC_HEADER ]; then\nmkdir -p \"$FRAMEWORK_HEADERS_DIR/$RELATIVE_PATH\"\nmv \"$FRAMEWORK_HEADERS_DIR/$PUBLIC_HEADER\" \"$FRAMEWORK_HEADERS_DIR/$RELATIVE_PATH/$PUBLIC_HEADER\"\nfi\ndone\nfi\n"; - }; - 66F2EBF51A8DC0CD00D536E9 /* Build Framework Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Build Framework Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\nset -e\n\nset +u\nif [[ $UFW_MASTER_SCRIPT_RUNNING ]]\nthen\n# Nothing for the slave script to do\nexit 0\nfi\nset -u\nexport UFW_MASTER_SCRIPT_RUNNING=1\n\n\n# Functions\n\n## List files in the specified directory, storing to the specified array.\n#\n# @param $1 The path to list\n# @param $2 The name of the array to fill\n#\n##\nlist_files ()\n{\n filelist=$(ls \"$1\")\n while read line\n do\n eval \"$2[\\${#$2[*]}]=\\\"\\$line\\\"\"\n done <<< \"$filelist\"\n}\n\n\n# Sanity check\n\nif [[ ! -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]]\nthen\necho \"Framework target \\\"${TARGET_NAME}\\\" had no source files to build from. Make sure your source files have the correct target membership\"\nexit 1\nfi\n\necho \"Target Executable: ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\n\n\n# Gather information\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nUFW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nUFW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$UFW_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nUFW_OTHER_PLATFORM=iphonesimulator\nelse\nUFW_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$UFW_SDK_PLATFORM$ ]]\nthen\nUFW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${UFW_OTHER_PLATFORM}\"\nelse\necho \"Could not find $UFW_SDK_PLATFORM in $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\nONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM:-$ONLY_ACTIVE_ARCH}\n\n# Short-circuit if all binaries are up to date.\n# We already checked the other platform in the prerun script.\n\nif [[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\" ]] && [[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${PRODUCT_NAME}\" ]] && [[ ! \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\" -nt \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${PRODUCT_NAME}\" ]]\nthen\nexit 0\nfi\n\nif [ \"${ONLY_ACTIVE_PLATFORM}\" == \"YES\" ]\nthen\necho \"ONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM}: Skipping other platform build\"\nelse\n# Make sure the other platform gets built\n\necho \"Build other platform\"\n\necho xcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" $ACTION\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" $ACTION\n\n\n# Build the fat static library binary\n\necho \"Create universal static library\"\n\necho \"$PLATFORM_DEVELOPER_BIN_DIR/libtool\" -static \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -o \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\"\n\"$PLATFORM_DEVELOPER_BIN_DIR/libtool\" -static \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -o \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\"\n\necho mv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}\"\nmv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}\"\nfi\n\n# Move executable to product name location\n\necho \"Moving Executable\"\necho \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\necho \"to\"\necho \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\nmv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\n# Build embedded framework structure\n\necho \"Build Embedded Framework\"\n\necho rm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\nrm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\necho mkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources\"\nmkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources\"\necho cp -a \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/\"\ncp -a \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/\"\n\ndeclare -a UFW_FILE_LIST\nlist_files \"${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" UFW_FILE_LIST\nfor filename in \"${UFW_FILE_LIST[@]}\"\ndo\nif [[ \"${filename}\" != \"Info.plist\" ]] && [[ ! \"${filename}\" =~ .*\\.lproj$ ]]\nthen\necho ln -sfh \"../${WRAPPER_NAME}/Resources/${filename}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources/${filename}\"\nln -sfh \"../${WRAPPER_NAME}/Resources/${filename}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources/${filename}\"\nfi\ndone\n\n\nif [ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]\nthen\n# Replace other platform's framework with a copy of this one (so that both have the same universal binary)\n\necho \"Copy from $UFW_SDK_PLATFORM to $UFW_OTHER_PLATFORM\"\n\necho rm -rf \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\nrm -rf \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\necho cp -a \"${BUILD_DIR}/${CONFIGURATION}-${UFW_SDK_PLATFORM}\" \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\ncp -a \"${BUILD_DIR}/${CONFIGURATION}-${UFW_SDK_PLATFORM}\" \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 66B9D2881A8D5FDE00CAC341 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 66B9D2A61A8D609200CAC341 /* PerformanceBezierAbstractTest.m in Sources */, - 66B9D2A71A8D609300CAC341 /* PerformanceBezierClockwiseTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 66F2EBDD1A8DC05100D536E9 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 66F2EBF61A8DC12600D536E9 /* UIBezierPath+Center.m in Sources */, - 66F2EBF71A8DC12800D536E9 /* UIBezierPath+Clockwise.m in Sources */, - 66F2EBFD1A8DC13D00D536E9 /* UIBezierPath+FirstLast.m in Sources */, - 66F2EBFC1A8DC13900D536E9 /* UIBezierPath+Uncached.m in Sources */, - 66F2EBFF1A8DC14100D536E9 /* JRSwizzle.m in Sources */, - 66F2EBFB1A8DC13700D536E9 /* UIBezierPath+Performance.m in Sources */, - 66F2EBFE1A8DC13F00D536E9 /* UIBezierPathProperties.m in Sources */, - 66AB566D1B0D9830005E6FB1 /* UIBezierPath+Util.m in Sources */, - 66F2EBF91A8DC13300D536E9 /* UIBezierPath+Description.m in Sources */, - 66F2EBFA1A8DC13500D536E9 /* UIBezierPath+NSOSX.m in Sources */, - 66F2EBF81A8DC13100D536E9 /* UIBezierPath+Equals.m in Sources */, - 9494C4A11F4715A000D5BCFD /* UIBezierPath+Trimming.m in Sources */, - 66D1B7981AFEAC6F00210262 /* UIBezierPath+Trim.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 6617530F1A8DC8300051D5CB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 66F2EBE21A8DC05100D536E9 /* PerformanceBezier */; - targetProxy = 6617530E1A8DC8300051D5CB /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 661B30CD1A7EB43A008549C7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - STRIP_INSTALLED_PRODUCT = NO; - STRIP_STYLE = "non-global"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 661B30CE1A7EB43A008549C7 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - SDKROOT = iphoneos; - STRIP_INSTALLED_PRODUCT = NO; - STRIP_STYLE = "non-global"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 66B9D2971A8D5FDF00CAC341 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - "$(BUILT_PRODUCTS_DIR)/**", - ); - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = PerformanceBezierTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC++", - "-lstdc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 66B9D2981A8D5FDF00CAC341 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - "$(BUILT_PRODUCTS_DIR)/**", - ); - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = PerformanceBezierTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC++", - "-lstdc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 66F2EBF11A8DC05100D536E9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = PerformanceBezier/Info.plist; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_PLATFORM = YES; - OTHER_LDFLAGS = "-ObjC++"; - PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - WRAPPER_EXTENSION = framework; - }; - name = Debug; - }; - 66F2EBF21A8DC05100D536E9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = PerformanceBezier/Info.plist; - MTL_ENABLE_DEBUG_INFO = NO; - ONLY_ACTIVE_PLATFORM = YES; - OTHER_LDFLAGS = "-ObjC++"; - PRODUCT_BUNDLE_IDENTIFIER = "com.milestonemade.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - WRAPPER_EXTENSION = framework; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 661B30CC1A7EB43A008549C7 /* Build configuration list for PBXProject "PerformanceBezier" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 661B30CD1A7EB43A008549C7 /* Debug */, - 661B30CE1A7EB43A008549C7 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 66B9D2961A8D5FDF00CAC341 /* Build configuration list for PBXNativeTarget "PerformanceBezierTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 66B9D2971A8D5FDF00CAC341 /* Debug */, - 66B9D2981A8D5FDF00CAC341 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 66F2EBF01A8DC05100D536E9 /* Build configuration list for PBXNativeTarget "PerformanceBezier" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 66F2EBF11A8DC05100D536E9 /* Debug */, - 66F2EBF21A8DC05100D536E9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 661B30C91A7EB43A008549C7 /* Project object */; -} diff --git a/ios/PerformanceBezier/PerformanceBezier/Info.plist b/ios/PerformanceBezier/PerformanceBezier/Info.plist deleted file mode 100644 index 2c15fb5ce..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h b/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h deleted file mode 100644 index 7d29bc2ea..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h +++ /dev/null @@ -1,13 +0,0 @@ -// JRSwizzle.h semver:1.0 -// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com -// Some rights reserved: http://opensource.org/licenses/MIT -// https://github.com/rentzsch/jrswizzle - -#import - -@interface NSObject (JRSwizzle) - -+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; -+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m b/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m deleted file mode 100644 index 4e582bf5a..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m +++ /dev/null @@ -1,134 +0,0 @@ -// JRSwizzle.m semver:1.0 -// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com -// Some rights reserved: http://opensource.org/licenses/MIT -// https://github.com/rentzsch/jrswizzle - -#import "JRSwizzle.h" - -#if TARGET_OS_IPHONE - #import - #import -#else - #import -#endif - -#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \ - if (ERROR_VAR) { \ - NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \ - *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \ - code:-1 \ - userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \ - } -#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__) - -#if OBJC_API_VERSION >= 2 -#define GetClass(obj) object_getClass(obj) -#else -#define GetClass(obj) (obj ? obj->isa : Nil) -#endif - -@implementation NSObject (JRSwizzle) - -+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ { -#if OBJC_API_VERSION >= 2 - Method origMethod = class_getInstanceMethod(self, origSel_); - if (!origMethod) { -#if TARGET_OS_IPHONE - SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); -#else - SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); -#endif - return NO; - } - - Method altMethod = class_getInstanceMethod(self, altSel_); - if (!altMethod) { -#if TARGET_OS_IPHONE - SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); -#else - SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); -#endif - return NO; - } - - class_addMethod(self, - origSel_, - class_getMethodImplementation(self, origSel_), - method_getTypeEncoding(origMethod)); - class_addMethod(self, - altSel_, - class_getMethodImplementation(self, altSel_), - method_getTypeEncoding(altMethod)); - - method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_)); - return YES; -#else - // Scan for non-inherited methods. - Method directOriginalMethod = NULL, directAlternateMethod = NULL; - - void *iterator = NULL; - struct objc_method_list *mlist = class_nextMethodList(self, &iterator); - while (mlist) { - int method_index = 0; - for (; method_index < mlist->method_count; method_index++) { - if (mlist->method_list[method_index].method_name == origSel_) { - assert(!directOriginalMethod); - directOriginalMethod = &mlist->method_list[method_index]; - } - if (mlist->method_list[method_index].method_name == altSel_) { - assert(!directAlternateMethod); - directAlternateMethod = &mlist->method_list[method_index]; - } - } - mlist = class_nextMethodList(self, &iterator); - } - - // If either method is inherited, copy it up to the target class to make it non-inherited. - if (!directOriginalMethod || !directAlternateMethod) { - Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL; - if (!directOriginalMethod) { - inheritedOriginalMethod = class_getInstanceMethod(self, origSel_); - if (!inheritedOriginalMethod) { - SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); - return NO; - } - } - if (!directAlternateMethod) { - inheritedAlternateMethod = class_getInstanceMethod(self, altSel_); - if (!inheritedAlternateMethod) { - SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); - return NO; - } - } - - int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1; - struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1))); - hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind - hoisted_method_list->method_count = hoisted_method_count; - Method hoisted_method = hoisted_method_list->method_list; - - if (!directOriginalMethod) { - bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method)); - directOriginalMethod = hoisted_method++; - } - if (!directAlternateMethod) { - bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method)); - directAlternateMethod = hoisted_method; - } - class_addMethods(self, hoisted_method_list); - } - - // Swizzle. - IMP temp = directOriginalMethod->method_imp; - directOriginalMethod->method_imp = directAlternateMethod->method_imp; - directAlternateMethod->method_imp = temp; - - return YES; -#endif -} - -+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ { - return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_]; -} - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist b/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist deleted file mode 100644 index c03188bc4..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier-Info.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - -CFBundleDevelopmentRegion - diff --git a/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h b/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h deleted file mode 100644 index aa2986e46..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// PerformanceBezier.h -// PerformanceBezier -// -// Created by Adam Wulf on 2/1/15. -// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. -// - -#define CGPointNotFound CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX) - -#import -#import "UIBezierPathProperties.h" -#import "UIBezierPath+Clockwise.h" -#import "UIBezierPath+Performance.h" -#import "UIBezierPath+NSOSX.h" -#import "UIBezierPath+Equals.h" -#import "UIBezierPath+Center.h" -#import "UIBezierPath+Trim.h" -#import "UIBezierPath+Util.h" -#import "UIBezierPath+Trimming.h" diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h deleted file mode 100644 index fc40810a2..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// UIBezierPath+Center.h -// ios-hand-shadows -// -// Created by Adam Wulf on 2/2/15. -// Copyright (c) 2015 Milestone Made. All rights reserved. -// - -#import - -@interface UIBezierPath (Center) - -// returns a point in the center of -// the path's bounds --(CGPoint) center; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m deleted file mode 100644 index 6273ccd18..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Center.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIBezierPath+Center.m -// ios-hand-shadows -// -// Created by Adam Wulf on 2/2/15. -// Copyright (c) 2015 Milestone Made. All rights reserved. -// - -#import "UIBezierPath+Center.h" - -@implementation UIBezierPath (Center) - - --(CGPoint) center{ - return CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); -} - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h deleted file mode 100644 index 146377de6..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIBezierPath+Clockwise.h -// PerformanceBezier -// -// Created by Adam Wulf on 1/7/14. -// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. -// - -#import - -@interface UIBezierPath (Clockwise) - -// -// returns YES if the path elements curve -// around in clockwise direction --(BOOL) isClockwise; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m deleted file mode 100644 index 18e34c84a..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Clockwise.m +++ /dev/null @@ -1,50 +0,0 @@ -// -// UIBezierPath+Clockwise.m -// PerformanceBezier -// -// Created by Adam Wulf on 1/7/14. -// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. -// - -#import "UIBezierPath+Clockwise.h" -#import "PerformanceBezier.h" - -@implementation UIBezierPath (Clockwise) - --(BOOL) isClockwise{ - - __block CGPoint lastMoveTo = CGPointZero; - __block CGPoint lastPoint = CGPointZero; - __block CGFloat sum = 0; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx){ - if(element.type == kCGPathElementMoveToPoint){ - lastMoveTo = element.points[0]; - lastPoint = lastMoveTo; - }else if(element.type == kCGPathElementAddLineToPoint){ - sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint]; - lastPoint = element.points[0]; - }else if(element.type == kCGPathElementAddQuadCurveToPoint){ - sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint]; - sum += [self calculateAreaFor:element.points[1] andPoint:element.points[0]]; - lastPoint = element.points[1]; - }else if(element.type == kCGPathElementAddCurveToPoint){ - sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint]; - sum += [self calculateAreaFor:element.points[1] andPoint:element.points[0]]; - sum += [self calculateAreaFor:element.points[2] andPoint:element.points[1]]; - lastPoint = element.points[2]; - }else if(element.type == kCGPathElementCloseSubpath){ - sum += [self calculateAreaFor:lastMoveTo andPoint:lastPoint]; - lastPoint = element.points[0]; - lastPoint = lastMoveTo; - } - }]; - return sum >= 0; -} - - -- (CGFloat) calculateAreaFor:(CGPoint)point1 andPoint:(CGPoint)point2{ - return (point2.x - point1.x) * (point2.y + point1.y); -} - - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h deleted file mode 100644 index b125ce4d4..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// UIBezierPath+Description.h -// LooseLeaf -// -// Created by Adam Wulf on 12/17/13. -// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. -// - -#import - -@interface UIBezierPath (Description) - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m deleted file mode 100644 index 371c61f40..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Description.m +++ /dev/null @@ -1,57 +0,0 @@ -// -// UIBezierPath+Description.m -// LooseLeaf -// -// Created by Adam Wulf on 12/17/13. -// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. -// - -#import "UIBezierPath+Description.h" -#import "UIBezierPath+NSOSX.h" -#import "JRSwizzle.h" - -@implementation UIBezierPath (Description) - - -// -// create a human readable objective-c string for -// the path. this lets a dev easily print out the bezier -// from the debugger, and copy the result directly back into -// code. Perfect for printing out runtime generated beziers -// for use later in tests. --(NSString*) swizzle_description{ - __block NSString* str = @"path = [UIBezierPath bezierPath];\n"; - [self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){ - if(ele.type == kCGPathElementAddCurveToPoint){ - CGPoint curveTo = ele.points[2]; - CGPoint ctrl1 = ele.points[0]; - CGPoint ctrl2 = ele.points[1]; - str = [str stringByAppendingFormat:@"[path addCurveToPoint:CGPointMake(%f, %f) controlPoint1:CGPointMake(%f, %f) controlPoint2:CGPointMake(%f, %f)];\n", curveTo.x, curveTo.y, ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y]; - }else if(ele.type == kCGPathElementAddLineToPoint){ - CGPoint lineTo = ele.points[0]; - str = [str stringByAppendingFormat:@"[path addLineToPoint:CGPointMake(%f, %f)];\n", lineTo.x, lineTo.y]; - }else if(ele.type == kCGPathElementAddQuadCurveToPoint){ - CGPoint curveTo = ele.points[2]; - CGPoint ctrl = ele.points[0]; - str = [str stringByAppendingFormat:@"[path addQuadCurveToPoint:CGPointMake(%f, %f) controlPoint:CGPointMake(%f, %f)];\n", curveTo.x, curveTo.y, ctrl.x, ctrl.y]; - }else if(ele.type == kCGPathElementCloseSubpath){ - [self closePath]; - str = [str stringByAppendingString:@"[path closePath];\n"]; - }else if(ele.type == kCGPathElementMoveToPoint){ - CGPoint moveTo = ele.points[0]; - str = [str stringByAppendingFormat:@"[path moveToPoint:CGPointMake(%f, %f)];\n", moveTo.x, moveTo.y]; - } - }]; - return str; -} - - -+(void)load{ - @autoreleasepool { - NSError *error = nil; - [UIBezierPath jr_swizzleMethod:@selector(description) - withMethod:@selector(swizzle_description) - error:&error]; - } -} -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h deleted file mode 100644 index 7b052d981..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIBezierPath+Equals.h -// LooseLeaf -// -// Created by Adam Wulf on 6/3/14. -// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. -// - -#import - -@interface UIBezierPath (Equals) - -// returns YES if the input path is equal -// to the current path. convenience wrapper -// around CGPathEqualToPath --(BOOL) isEqualToBezierPath:(UIBezierPath*)path; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m deleted file mode 100644 index 8649e471b..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Equals.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIBezierPath+Debug.m -// LooseLeaf -// -// Created by Adam Wulf on 6/3/14. -// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. -// - -#import "UIBezierPath+Equals.h" -#import "PerformanceBezier.h" - -@implementation UIBezierPath (Equals) - --(BOOL) isEqualToBezierPath:(UIBezierPath*)path{ - return CGPathEqualToPath(self.CGPath, path.CGPath); -} - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h deleted file mode 100644 index 14e2bc8fb..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// UIBezierPath+FirstLast.h -// iOS-UIBezierPath-Performance -// -// Created by Adam Wulf on 2/1/15. -// -// - -#import - -@interface UIBezierPath (FirstLast) - -// calculates the first point of the path, -// useful if its not already cached --(CGPoint) lastPointCalculated; - -// calculates the last point of the path, -// useful if its not already cached --(CGPoint) firstPointCalculated; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m deleted file mode 100644 index 89c98cea9..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+FirstLast.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// UIBezierPath+FirstLast.m -// iOS-UIBezierPath-Performance -// -// Created by Adam Wulf on 2/1/15. -// -// - -#import "UIBezierPath+FirstLast.h" -#import "UIBezierPath+NSOSX.h" - -@implementation UIBezierPath (FirstLast) - --(CGPoint) lastPointCalculated{ - __block CGPoint firstPoint = CGPointZero; - __block CGPoint lastPoint = CGPointZero; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { - CGPoint currPoint = CGPointZero; - if(element.type == kCGPathElementMoveToPoint){ - currPoint = element.points[0]; - firstPoint = currPoint; - }else if(element.type == kCGPathElementAddLineToPoint){ - currPoint = element.points[0]; - }else if(element.type == kCGPathElementCloseSubpath){ - currPoint = firstPoint; - }else if(element.type == kCGPathElementAddCurveToPoint){ - currPoint = element.points[2]; - }else if(element.type == kCGPathElementAddQuadCurveToPoint){ - currPoint = element.points[1]; - } - if(idx == 0){ - // path should've begun with a moveTo, - // but this is a sanity check for malformed - // paths - firstPoint = currPoint; - } - lastPoint = currPoint; - }]; - return lastPoint; -} - --(CGPoint) firstPointCalculated{ - __block CGPoint firstPoint = CGPointZero; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { - if(idx == 0){ - if(element.type == kCGPathElementMoveToPoint || - element.type == kCGPathElementAddLineToPoint){ - firstPoint = element.points[0]; - }else if(element.type == kCGPathElementCloseSubpath){ - firstPoint = firstPoint; - }else if(element.type == kCGPathElementAddCurveToPoint){ - firstPoint = element.points[2]; - }else if(element.type == kCGPathElementAddQuadCurveToPoint){ - firstPoint = element.points[1]; - } - } - }]; - return firstPoint; -} - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h deleted file mode 100644 index 53732baa3..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// UIBezierPath+NSOSX.h -// PaintingSample -// -// Created by Adam Wulf on 10/5/12. -// -// - -#import - -@interface UIBezierPath (NSOSX) - -// A flattened version of the path object. -@property(nonatomic,readonly) UIBezierPath* bezierPathByFlatteningPath; -// returns the number of elements in this path -@property(nonatomic,readonly) NSInteger elementCount; -// YES if the path is made without curves, NO otherwise -@property(nonatomic,assign) BOOL isFlat; - --(UIBezierPath*) bezierPathByFlatteningPathAndImmutable:(BOOL)returnCopy; - -// returns the element at the given index, and also -// fills the points[] array with the element's points. -// the points property of the CGPathElement is owned by -// the internal cache, so if you need the points, you should -// retrieve them through the points parameter. -- (CGPathElement)elementAtIndex:(NSInteger)index associatedPoints:(CGPoint[])points; - -// returns the element at the given index. If you also need -// access to the element's points, then use the method above. -- (CGPathElement)elementAtIndex:(NSInteger)index; - -// modifies the element at the index to have the input -// points associated with it. This allows modifying the -// path in place -- (void)setAssociatedPoints:(CGPoint[])points atIndex:(NSInteger)index; - -// returns the bounds of the path including its control points --(CGRect) controlPointBounds; - -// iterate over each element in the path with the input block --(void) iteratePathWithBlock:(void (^)(CGPathElement element,NSUInteger idx))block; - -// helper method to return the number of points for any input element -// based on its type. ie, an element of type -// kCGPathElementAddCurveToPoint returns 3 -+(NSInteger) numberOfPointsForElement:(CGPathElement)element; - -// helper method to copy a path element to a new element. -// Note: you are responsible for calling free(yourElement.points) -// when you are done with its return value. -+(CGPathElement*) copyCGPathElement:(CGPathElement*)element; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m deleted file mode 100644 index 320dffb72..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m +++ /dev/null @@ -1,522 +0,0 @@ -// -// UIBezierPath+NSOSX.m -// PaintingSample -// -// Created by Adam Wulf on 10/5/12. -// -// - -#import "UIBezierPath+NSOSX.h" -#import -#import "JRSwizzle.h" -#import "UIBezierPath+NSOSX_Private.h" -#import "UIBezierPath+Performance_Private.h" -#import "UIBezierPath+Performance.h" -#import "UIBezierPath+Uncached.h" -#import "UIBezierPath+Util.h" - - -static char ELEMENT_ARRAY; -static CGFloat idealFlatness = .01; - -@implementation UIBezierPath (NSOSX) - - -#pragma mark - Properties - -/** - * this is a property on the category, as described in: - * https://github.com/techpaa/iProperties - * - * - * this array is for private PerformanceBezier use only - * - * Since iOS doesn't allow for index lookup of CGPath elements (only option is CGPathApply) - * this array will cache the elements after they've been looked up once - */ --(void) freeCurrentElementCacheArray{ - NSMutableArray* currentArray = objc_getAssociatedObject(self, &ELEMENT_ARRAY); - if([currentArray count]){ - while([currentArray count]){ - NSValue* val = [currentArray lastObject]; - CGPathElement* element = [val pointerValue]; - free(element->points); - free(element); - [currentArray removeLastObject]; - } - } - objc_setAssociatedObject(self, &ELEMENT_ARRAY, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} --(void)setElementCacheArray:(NSMutableArray *)_elementCacheArray{ - [self freeCurrentElementCacheArray]; - NSMutableArray* newArray = [NSMutableArray array]; - for(NSValue* val in _elementCacheArray){ - CGPathElement* element = [val pointerValue]; - CGPathElement* copiedElement = [UIBezierPath copyCGPathElement:element]; - [newArray addObject:[NSValue valueWithPointer:copiedElement]]; - } - objc_setAssociatedObject(self, &ELEMENT_ARRAY, newArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} --(NSMutableArray*)elementCacheArray{ - NSMutableArray* ret = objc_getAssociatedObject(self, &ELEMENT_ARRAY); - if(!ret){ - ret = [NSMutableArray array]; - self.elementCacheArray = ret; - } - return ret; -} - - - -#pragma mark - UIBezierPath - -/** - * returns the CGPathElement at the specified index, optionally - * also returning the elements points in the 2nd parameter - * - * this method is meant to mimic UIBezierPath's method of the same name - */ -- (CGPathElement)elementAtIndex:(NSInteger)askingForIndex associatedPoints:(CGPoint[])points{ - __block BOOL didReturn = NO; - __block CGPathElement returnVal; - if(askingForIndex < [self.elementCacheArray count]){ - returnVal = *(CGPathElement*)[[self.elementCacheArray objectAtIndex:askingForIndex] pointerValue]; -#ifdef MMPreventBezierPerformance - [self simulateNoBezierCaching]; -#endif - }else{ - __block UIBezierPath* this = self; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ - int numberInCache = (int) [this.elementCacheArray count]; - if(!didReturn || currentIndex == [this.elementCacheArray count]){ - if(currentIndex == numberInCache){ - [this.elementCacheArray addObject:[NSValue valueWithPointer:[UIBezierPath copyCGPathElement:&element]]]; - } - if(currentIndex == askingForIndex){ - returnVal = *(CGPathElement*)[[this.elementCacheArray objectAtIndex:askingForIndex] pointerValue]; - didReturn = YES; - } - } - }]; - } - - if(points){ - for(int i=0;i<[UIBezierPath numberOfPointsForElement:returnVal];i++){ - points[i] = returnVal.points[i]; - } - } - return returnVal; -} - - -/** - * returns the CGPathElement at the specified index - * - * this method is meant to mimic UIBezierPath's method of the same name - */ -- (CGPathElement)elementAtIndex:(NSInteger)index{ - return [self elementAtIndex:index associatedPoints:NULL]; -} - - -/** - * updates the point in the path with the new input points - * - * TODO: this method is entirely untested - */ -- (void)setAssociatedPoints:(CGPoint[])points atIndex:(NSInteger)index{ - NSMutableDictionary* params = [NSMutableDictionary dictionary]; - [params setObject:[NSNumber numberWithInteger:index] forKey:@"index"]; - [params setObject:[NSValue valueWithPointer:points] forKey:@"points"]; - CGPathApply(self.CGPath, params, updatePathElementAtIndex); - -} -// -// helper function for the setAssociatedPoints: method -void updatePathElementAtIndex(void* info, const CGPathElement* element) { - NSMutableDictionary* params = (NSMutableDictionary*)info; - int currentIndex = 0; - if([params objectForKey:@"curr"]){ - currentIndex = [[params objectForKey:@"curr"] intValue] + 1; - } - if(currentIndex == [[params objectForKey:@"index"] intValue]){ - CGPoint* points = [[params objectForKey:@"points"] pointerValue]; - for(int i=0;i<[UIBezierPath numberOfPointsForElement:*element];i++){ - element->points[i] = points[i]; - } - CGPathElement* returnVal = [UIBezierPath copyCGPathElement:(CGPathElement*)element]; - [params setObject:[NSValue valueWithPointer:returnVal] forKey:@"element"]; - } - [params setObject:[NSNumber numberWithInt:currentIndex] forKey:@"curr"]; -} - -/** - * Returns the bounding box containing all points in a graphics path. - * The bounding box is the smallest rectangle completely enclosing - * all points in the path, including control points for Bézier and - * quadratic curves. - * - * this method is meant to mimic UIBezierPath's method of the same name - */ --(CGRect) controlPointBounds{ - return CGPathGetBoundingBox(self.CGPath); -} - - -- (NSInteger)elementCount{ - UIBezierPathProperties* props = [self pathProperties]; - if(props.cachedElementCount){ -#ifdef MMPreventBezierPerformance - [self simulateNoBezierCaching]; -#endif - return props.cachedElementCount; - } - NSMutableDictionary* params = [NSMutableDictionary dictionary]; - [params setObject:[NSNumber numberWithInteger:0] forKey:@"count"]; - [params setObject:self forKey:@"self"]; - [self retain]; - CGPathApply(self.CGPath, params, countPathElement); - [self release]; - NSInteger ret = [[params objectForKey:@"count"] integerValue]; - props.cachedElementCount = ret; - return ret; -} -// helper function -void countPathElement(void* info, const CGPathElement* element) { - NSMutableDictionary* params = (NSMutableDictionary*) info; - UIBezierPath* this = [params objectForKey:@"self"]; - NSInteger count = [[params objectForKey:@"count"] integerValue]; - [params setObject:[NSNumber numberWithInteger:(count + 1)] forKey:@"count"]; - if(count == [this.elementCacheArray count]){ - [this.elementCacheArray addObject:[NSValue valueWithPointer:[UIBezierPath copyCGPathElement:(CGPathElement*)element]]]; - } -} - --(void) iteratePathWithBlock:(void (^)(CGPathElement element,NSUInteger idx))block{ - void (^copiedBlock)(CGPathElement element) = [block copy]; - NSMutableDictionary* params = [NSMutableDictionary dictionary]; - [params setObject:copiedBlock forKey:@"block"]; - CGPathApply(self.CGPath, params, blockWithElement); - [copiedBlock release]; -} - -// helper function -static void blockWithElement(void* info, const CGPathElement* element) { - NSMutableDictionary* params = (NSMutableDictionary*) info; - void (^block)(CGPathElement element,NSUInteger idx) = [params objectForKey:@"block"]; - NSUInteger index = [[params objectForKey:@"index"] unsignedIntegerValue]; - block(*element, index); - [params setObject:@(index+1) forKey:@"index"]; -} - -#pragma mark - Flat - - - -#pragma mark - Properties - - -/** - * this is a property on the category, as described in: - * https://github.com/techpaa/iProperties - */ --(void)setIsFlat:(BOOL)isFlat{ - [self pathProperties].isFlat = isFlat; -} - -/** - * return YES if this bezier path is made up of only - * moveTo, closePath, and lineTo elements - * - * TODO - * this method helps caching flattened paths internally - * to this category, but is not yet fit for public use. - * - * detecting when this path is flat would mean we'd have - * to also swizzle the constructors to bezier paths - */ --(BOOL) isFlat{ - return [self pathProperties].isFlat; -} - -#pragma mark - UIBezierPath - - -/** - * call this method on a UIBezierPath to generate - * a new flattened path - * - * This category is named after Athar Luqman Ahmad, who - * wrote a masters thesis about minimizing the number of - * lines required to flatten a bezier curve - * - * The thesis is available here: - * http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf - * - * The algorithm that I use as of 10/09/2012 is a simple - * recursive algorithm that doesn't use any of ahmed's - * optimizations yet - * - * TODO: add in Ahmed's optimizations - */ --(UIBezierPath*) bezierPathByFlatteningPath{ - return [self bezierPathByFlatteningPathAndImmutable:NO]; -} -/** - * @param shouldBeImmutable: YES if this function should return a distinct UIBezier, NO otherwise - * - * if the caller plans to modify the returned path, then shouldBeImmutable should - * be called with NO. - * - * if the caller only plans to iterate over and look at the returned value, - * then shouldBeImmutable should be YES - this is considerably faster to not - * return a copy if the value will be treated as immutable - */ --(UIBezierPath*) bezierPathByFlatteningPathAndImmutable:(BOOL)willBeImmutable{ - UIBezierPathProperties* props = [self pathProperties]; - UIBezierPath* ret = props.bezierPathByFlatteningPath; - if(ret){ - if(willBeImmutable) return ret; - return [[ret copy] autorelease]; - } - if(self.isFlat){ - if(willBeImmutable) return self; - return [[self copy] autorelease]; - } - - __block NSInteger flattenedElementCount = 0; - UIBezierPath *newPath = [UIBezierPath bezierPath]; - NSInteger elements = [self elementCount]; - NSInteger n; - CGPoint pointForClose = CGPointMake (0.0, 0.0); - CGPoint lastPoint = CGPointMake (0.0, 0.0); - - for (n = 0; n < elements; ++n) - { - CGPoint points[3]; - CGPathElement element = [self elementAtIndex:n associatedPoints:points]; - - switch (element.type) - { - case kCGPathElementMoveToPoint: - [newPath moveToPoint:points[0]]; - pointForClose = lastPoint = points[0]; - flattenedElementCount++; - continue; - - case kCGPathElementAddLineToPoint: - [newPath addLineToPoint:points[0]]; - lastPoint = points[0]; - flattenedElementCount++; - break; - - case kCGPathElementAddQuadCurveToPoint: - case kCGPathElementAddCurveToPoint: - { - - // - // handle both curve types gracefully - CGPoint curveTo; - CGPoint ctrl1; - CGPoint ctrl2; - if(element.type == kCGPathElementAddQuadCurveToPoint){ - curveTo = element.points[1]; - ctrl1 = element.points[0]; - ctrl2 = ctrl1; - }else if(element.type == kCGPathElementAddCurveToPoint){ - curveTo = element.points[2]; - ctrl1 = element.points[0]; - ctrl2 = element.points[1]; - } - - // - // ok, this is the bezier for our current element - CGPoint bezier[4] = { lastPoint, ctrl1, ctrl2, curveTo }; - - - // - // define our recursive function that will - // help us split the curve up as needed - void (^__block flattenCurve)(UIBezierPath* newPath, CGPoint startPoint, CGPoint bez[4]) = ^(UIBezierPath* newPath, CGPoint startPoint, CGPoint bez[4]){ - // - // first, calculate the error rate for - // a line segement between the start/end points - // vs the curve - - CGPoint onCurve = bezierPointAtT(bez, .5); - - CGFloat error = distanceOfPointToLine(onCurve, startPoint, bez[2]); - - - // - // if that error is less than our accepted - // level of error, then just add a line, - // - // otherwise, split the curve in half and recur - if (error <= idealFlatness) - { - [newPath addLineToPoint:bez[3]]; - flattenedElementCount++; - } - else - { - CGPoint bez1[4], bez2[4]; - subdivideBezierAtT(bez, bez1, bez2, .5); - // now we've split the curve in half, and have - // two bezier curves bez1 and bez2. recur - // on these two halves - flattenCurve(newPath, startPoint, bez1); - flattenCurve(newPath, startPoint, bez2); - } - }; - - flattenCurve(newPath, lastPoint, bezier); - - lastPoint = points[2]; - break; - } - - case kCGPathElementCloseSubpath: - [newPath closePath]; - lastPoint = pointForClose; - flattenedElementCount++; - break; - - default: - break; - } - } - - // since we just built the flattened path - // we know how many elements there are, so cache that - UIBezierPathProperties* newPathProps = [newPath pathProperties]; - newPathProps.cachedElementCount = flattenedElementCount; - - props.bezierPathByFlatteningPath = newPath; - - return [self bezierPathByFlatteningPathAndImmutable:willBeImmutable]; -} - - -#pragma mark - Helper - -/** - * returns the length of the points array for the input - * CGPathElement element - */ -+(NSInteger) numberOfPointsForElement:(CGPathElement)element{ - NSInteger nPoints = 0; - switch (element.type) - { - case kCGPathElementMoveToPoint: - nPoints = 1; - break; - case kCGPathElementAddLineToPoint: - nPoints = 1; - break; - case kCGPathElementAddQuadCurveToPoint: - nPoints = 2; - break; - case kCGPathElementAddCurveToPoint: - nPoints = 3; - break; - case kCGPathElementCloseSubpath: - nPoints = 0; - break; - default: - nPoints = 0; - } - return nPoints; -} - - -/** - * copies the input CGPathElement - * - * TODO: I currently never free the memory assigned for the points array - * https://github.com/adamwulf/DrawKit-iOS/issues/4 - */ -+(CGPathElement*) copyCGPathElement:(CGPathElement*)element{ - CGPathElement* ret = malloc(sizeof(CGPathElement)); - if(!ret){ - @throw [NSException exceptionWithName:@"Memory Exception" reason:@"can't malloc" userInfo:nil]; - } - NSInteger numberOfPoints = [UIBezierPath numberOfPointsForElement:*element]; - if(numberOfPoints){ - ret->points = malloc(sizeof(CGPoint) * numberOfPoints); - }else{ - ret->points = NULL; - } - ret->type = element->type; - - for(int i=0;ipoints[i] = element->points[i]; - } - return ret; -} - - - - -#pragma mark - Swizzling - -/////////////////////////////////////////////////////////////////////////// -// -// All of these methods are to listen to UIBezierPath method calls -// so that we can add new functionality on top of them without -// changing any of the default behavior. -// -// These methods help maintain: -// 1. cachedElementCount -// 2. elementCacheArray -// 3. keeping cache's valid across copying - - --(void) nsosx_swizzle_removeAllPoints{ - [self setElementCacheArray:nil]; - [self nsosx_swizzle_removeAllPoints]; -} - --(UIBezierPath*) nsosx_swizzle_copy{ - UIBezierPath* ret = [self nsosx_swizzle_copy]; - // note, when setting the array here, it will actually be making - // a mutable copy of the input array, so the copied - // path will have its own version. - [ret setElementCacheArray:self.elementCacheArray]; - return ret; -} --(void) nsosx_swizzle_applyTransform:(CGAffineTransform)transform{ - [self setElementCacheArray:nil]; - [self pathProperties].hasLastPoint = NO; - [self pathProperties].hasFirstPoint = NO; - [self nsosx_swizzle_applyTransform:transform]; -} - - - --(void) nsosx_swizzle_dealloc{ - [self freeCurrentElementCacheArray]; - [self nsosx_swizzle_dealloc]; -} - -+(void)load{ - @autoreleasepool { - NSError *error = nil; - [UIBezierPath jr_swizzleMethod:@selector(removeAllPoints) - withMethod:@selector(nsosx_swizzle_removeAllPoints) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(applyTransform:) - withMethod:@selector(nsosx_swizzle_applyTransform:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(copy) - withMethod:@selector(nsosx_swizzle_copy) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(dealloc) - withMethod:@selector(nsosx_swizzle_dealloc) - error:&error]; - } -} - - - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h deleted file mode 100644 index 91dd1c789..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX_Private.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// UIBezierPath+NSOSX_Private.h -// PerformanceBezier -// -// Created by Adam Wulf on 10/9/12. -// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. -// - -#ifndef PerformanceBezier_UIBezierPath_NSOSX_Private_h -#define PerformanceBezier_UIBezierPath_NSOSX_Private_h - - -@interface UIBezierPath (NSOSX_Private) - -// cache of path elements -@property(nonatomic,retain) NSMutableArray* elementCacheArray; - -// cache of element count -@property(nonatomic,assign) NSInteger cachedElementCount; - -// helper functions to prime the above caches -void countPathElement(void* info, const CGPathElement* element); - -void updatePathElementAtIndex(void* info, const CGPathElement* element); - -@end - - -#endif diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h deleted file mode 100644 index 58f56c936..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// UIBezierPath+Performance.h -// PerformanceBezier -// -// Created by Adam Wulf on 1/31/15. -// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. -// - -#import -#import "UIBezierPathProperties.h" - -CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t); - -@interface UIBezierPath (Performance) - --(UIBezierPathProperties*) pathProperties; - -// returns the last point of the bezier path. -// if the path ends with a kCGPathElementClosed, -// then the first point of that subpath is returned --(CGPoint) lastPoint; - -// returns the first point of the bezier path --(CGPoint) firstPoint; - -// returns the tangent at the very end of the path -// in radians --(CGFloat) tangentAtEnd; - -// returns YES if the path is closed (or contains at least 1 closed subpath) -// returns NO otherwise --(BOOL) isClosed; - -// returns the tangent of the bezier path at the given t value -- (CGPoint) tangentOnPathAtElement:(NSInteger)elementIndex andTValue:(CGFloat)tVal; - -// for the input bezier curve [start, ctrl1, ctrl2, end] -// return the point at the input T value -+(CGPoint) pointAtT:(CGFloat)t forBezier:(CGPoint*)bez; - -// for the input bezier curve [start, ctrl1, ctrl2, end] -// return the tangent at the input T value -+(CGPoint) tangentAtT:(CGFloat)t forBezier:(CGPoint*)bez; - -// fill the input point array with [start, ctrl1, ctrl2, end] -// for the element at the given index --(void) fillBezier:(CGPoint[4])bezier forElement:(NSInteger)elementIndex; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m deleted file mode 100644 index 11cf809a5..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance.m +++ /dev/null @@ -1,525 +0,0 @@ -// -// UIBezierPath+Performance.m -// PerformanceBezier -// -// Created by Adam Wulf on 1/31/15. -// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. -// - -#import "UIBezierPath+Performance.h" -#import "UIBezierPath+Performance_Private.h" -#import "UIBezierPath+FirstLast.h" -#import "UIBezierPath+NSOSX.h" -#import "UIBezierPath+Uncached.h" -#import -#import "JRSwizzle.h" - -static char BEZIER_PROPERTIES; - -@implementation UIBezierPath (Performance) - --(UIBezierPathProperties*) pathProperties{ - UIBezierPathProperties* props = objc_getAssociatedObject(self, &BEZIER_PROPERTIES); - if(!props){ - props = [[[UIBezierPathProperties alloc] init] autorelease]; - objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - return props; -} - --(void)setTangentAtEnd:(CGFloat)tangent{ - [self pathProperties].tangentAtEnd = tangent; -} - --(CGPoint)lastPoint{ - UIBezierPathProperties* props = [self pathProperties]; - if(!props.hasLastPoint){ - props.hasLastPoint = YES; - props.lastPoint = [self lastPointCalculated]; -#ifdef MMPreventBezierPerformance - }else{ - [self simulateNoBezierCaching]; -#endif - } - return props.lastPoint; -} --(CGPoint)firstPoint{ - UIBezierPathProperties* props = [self pathProperties]; - if(!props.hasFirstPoint){ - props.hasFirstPoint = YES; - props.firstPoint = [self firstPointCalculated]; -#ifdef MMPreventBezierPerformance - }else{ - [self simulateNoBezierCaching]; -#endif - } - return props.firstPoint; -} --(BOOL) isClosed{ - UIBezierPathProperties* props = [self pathProperties]; - if(!props.knowsIfClosed){ - // we dont know if the path is closed, so - // find a close element if we have one - [self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){ - if(ele.type == kCGPathElementCloseSubpath){ - props.isClosed = YES; - } - }]; - props.knowsIfClosed = YES; -#ifdef MMPreventBezierPerformance - }else{ - [self simulateNoBezierCaching]; -#endif - } - return props.isClosed; -} --(CGFloat) tangentAtEnd{ -#ifdef MMPreventBezierPerformance - [self simulateNoBezierCaching]; -#endif - return [self pathProperties].tangentAtEnd; -} - -/** - * this is a property on the category, as described in: - * https://github.com/techpaa/iProperties - */ --(void)setBezierPathByFlatteningPath:(UIBezierPath *)bezierPathByFlatteningPath{ - [self pathProperties].bezierPathByFlatteningPath = bezierPathByFlatteningPath; -} - - - -/** - * this is a property on the category, as described in: - * https://github.com/techpaa/iProperties - * - * - * this is for internal PerformanceBezier use only - * - * Since iOS doesn't allow a quick lookup for element count, - * this property will act as a cache for the element count after - * it has been calculated once - */ --(void)setCachedElementCount:(NSInteger)_cachedElementCount{ - [self pathProperties].cachedElementCount = _cachedElementCount; -} - --(NSInteger)cachedElementCount{ - return [self pathProperties].cachedElementCount; -} - - - - --(void) fillBezier:(CGPoint[4])bezier forElement:(NSInteger)elementIndex{ - if(elementIndex >= [self elementCount] || elementIndex < 0){ - @throw [NSException exceptionWithName:@"BezierElementException" reason:@"Element index is out of range" userInfo:nil]; - } - if(elementIndex == 0){ - bezier[0] = self.firstPoint; - bezier[1] = self.firstPoint; - bezier[2] = self.firstPoint; - bezier[3] = self.firstPoint; - return; - } - - CGPathElement previousElement = [self elementAtIndex:elementIndex-1]; - CGPathElement thisElement = [self elementAtIndex:elementIndex]; - - if(previousElement.type == kCGPathElementMoveToPoint || - previousElement.type == kCGPathElementAddLineToPoint){ - bezier[0] = previousElement.points[0]; - }else if(previousElement.type == kCGPathElementAddQuadCurveToPoint){ - bezier[0] = previousElement.points[1]; - }else if(previousElement.type == kCGPathElementAddCurveToPoint){ - bezier[0] = previousElement.points[2]; - } - - if(thisElement.type == kCGPathElementCloseSubpath){ - bezier[1] = bezier[0]; - bezier[2] = self.firstPoint; - bezier[3] = self.firstPoint; - }else if (thisElement.type == kCGPathElementMoveToPoint || - thisElement.type == kCGPathElementAddLineToPoint){ -// bezier[1] = CGPointMake(bezier[0].x + (thisElement.points[0].x - bezier[0].x)/3, -// bezier[0].y + (thisElement.points[0].y - bezier[0].y)/3); -// bezier[2] = CGPointMake(bezier[0].x + (thisElement.points[0].x - bezier[0].x)*2/3, -// bezier[0].y + (thisElement.points[0].y - bezier[0].y)*2/3); - bezier[1] = bezier[0]; - bezier[2] = thisElement.points[0]; - bezier[3] = thisElement.points[0]; - }else if (thisElement.type == kCGPathElementAddQuadCurveToPoint){ - bezier[1] = thisElement.points[0]; - bezier[2] = thisElement.points[0]; - bezier[3] = thisElement.points[1]; - }else if (thisElement.type == kCGPathElementAddCurveToPoint){ - bezier[1] = thisElement.points[0]; - bezier[2] = thisElement.points[1]; - bezier[3] = thisElement.points[2]; - } -} - -- (CGPoint) tangentOnPathAtElement:(NSInteger)elementIndex andTValue:(CGFloat)tVal{ - if(elementIndex >= [self elementCount] || elementIndex < 0){ - @throw [NSException exceptionWithName:@"BezierElementException" reason:@"Element index is out of range" userInfo:nil]; - } - if(elementIndex == 0){ - return self.firstPoint; - } - - CGPoint bezier[4]; - - [self fillBezier:bezier forElement:elementIndex]; - return [UIBezierPath tangentAtT:tVal forBezier:bezier]; -} - - - -+(CGPoint) pointAtT:(CGFloat)t forBezier:(CGPoint*)bez{ - return bezierPointAtT(bez, t); -} - -+(CGPoint) tangentAtT:(CGFloat)t forBezier:(CGPoint*)bez{ - return bezierTangentAtT(bez, t); -} - - - - - - - - -#pragma mark - Swizzle - -/////////////////////////////////////////////////////////////////////////// -// -// All of these methods are to listen to UIBezierPath method calls -// so that we can add new functionality on top of them without -// changing any of the default behavior. -// -// These methods help maintain: -// 1. the cached flat version of this path -// 2. the flag for if this path is already flat or not - - --(void) ahmed_swizzle_dealloc{ - objc_setAssociatedObject(self, &BEZIER_PROPERTIES, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [self ahmed_swizzle_dealloc]; -} - -- (id)swizzle_initWithCoder:(NSCoder *)decoder{ - self = [self swizzle_initWithCoder:decoder]; - UIBezierPathProperties* props = [decoder decodeObjectForKey:@"pathProperties"]; - objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return self; -} - --(void) swizzle_encodeWithCoder:(NSCoder *)aCoder{ - [self swizzle_encodeWithCoder:aCoder]; - [aCoder encodeObject:self.pathProperties forKey:@"pathProperties"]; -} --(void) ahmed_swizzle_applyTransform:(CGAffineTransform)transform{ - // reset our path properties - BOOL isClosed = [self pathProperties].isClosed; - UIBezierPathProperties* props = [[[UIBezierPathProperties alloc] init] autorelease]; - props.isClosed = isClosed; - objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [self ahmed_swizzle_applyTransform:transform]; -} - --(void) swizzle_moveToPoint:(CGPoint)point{ - UIBezierPathProperties* props = [self pathProperties]; - props.bezierPathByFlatteningPath = nil; - BOOL isEmpty = [self isEmpty]; - if(isEmpty || props.isFlat){ - props.isFlat = YES; - } - if(isEmpty){ - props.hasFirstPoint = YES; - props.firstPoint = point; - props.cachedElementCount = 1; - }else if(props.cachedElementCount){ - if(!props.lastAddedElementWasMoveTo){ - // when adding multiple moveTo elements to a path - // in a row, iOS actually just modifies the last moveTo - // instead of having tons of useless moveTos - props.cachedElementCount = props.cachedElementCount + 1; - }else if(props.cachedElementCount == 1){ - // otherwise, the first and only point was - // a move to, so update our first point - props.firstPoint = point; - } - } - props.hasLastPoint = YES; - props.lastPoint = point; - props.tangentAtEnd = 0; - props.lastAddedElementWasMoveTo = YES; - [self swizzle_moveToPoint:point]; -} --(void) swizzle_addLineToPoint:(CGPoint)point{ - UIBezierPathProperties* props = [self pathProperties]; - props.lastAddedElementWasMoveTo = NO; - props.bezierPathByFlatteningPath = nil; - if([self isEmpty] || props.isFlat){ - props.isFlat = YES; - } - if(props.cachedElementCount){ - props.cachedElementCount = props.cachedElementCount + 1; - } - props.tangentAtEnd = [self calculateTangentBetween:point andPoint:props.lastPoint]; - props.hasLastPoint = YES; - props.lastPoint = point; - [self swizzle_addLineToPoint:point]; -} --(void) swizzle_addCurveToPoint:(CGPoint)point controlPoint1:(CGPoint)ctrl1 controlPoint2:(CGPoint)ctrl2{ - UIBezierPathProperties* props = [self pathProperties]; - props.lastAddedElementWasMoveTo = NO; - props.bezierPathByFlatteningPath = nil; - if([self isEmpty] || props.isFlat){ - props.isFlat = NO; - } - if(props.cachedElementCount){ - props.cachedElementCount = props.cachedElementCount + 1; - } - props.tangentAtEnd = [self calculateTangentBetween:point andPoint:ctrl2]; - props.hasLastPoint = YES; - props.lastPoint = point; - [self swizzle_addCurveToPoint:point controlPoint1:ctrl1 controlPoint2:ctrl2]; -} --(void) swizzle_quadCurveToPoint:(CGPoint)point controlPoint:(CGPoint)ctrl1{ - UIBezierPathProperties* props = [self pathProperties]; - props.lastAddedElementWasMoveTo = NO; - props.bezierPathByFlatteningPath = nil; - if([self isEmpty] || props.isFlat){ - props.isFlat = NO; - } - if(props.cachedElementCount){ - props.cachedElementCount = props.cachedElementCount + 1; - } - props.hasLastPoint = YES; - props.lastPoint = point; - props.tangentAtEnd = [self calculateTangentBetween:point andPoint:ctrl1]; - [self swizzle_quadCurveToPoint:point controlPoint:ctrl1]; -} --(void) swizzle_closePath{ - UIBezierPathProperties* props = [self pathProperties]; - props.isClosed = YES; - props.knowsIfClosed = YES; - props.lastAddedElementWasMoveTo = NO; - props.bezierPathByFlatteningPath = nil; - if([self isEmpty] || props.isFlat){ - props.isFlat = YES; - } - if(props.cachedElementCount){ - props.cachedElementCount = props.cachedElementCount + 1; - } - if(props.hasLastPoint && props.hasFirstPoint){ - props.lastPoint = props.firstPoint; - }else{ - props.hasLastPoint = NO; - } - [self swizzle_closePath]; -} --(void)swizzle_arcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise{ - UIBezierPathProperties* props = [self pathProperties]; - props.lastAddedElementWasMoveTo = NO; - props.bezierPathByFlatteningPath = nil; - if([self isEmpty] || props.isFlat){ - props.isFlat = NO; - } - if(props.cachedElementCount){ - props.cachedElementCount = props.cachedElementCount + 1; - } - [self swizzle_arcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:clockwise]; -} --(void) swizzle_removeAllPoints{ - UIBezierPathProperties* props = [self pathProperties]; - props.lastAddedElementWasMoveTo = NO; - props.bezierPathByFlatteningPath = nil; - [self swizzle_removeAllPoints]; - props.cachedElementCount = 0; - props.tangentAtEnd = 0; - props.hasLastPoint = NO; - props.hasFirstPoint = NO; - props.isClosed = NO; - props.knowsIfClosed = YES; - if([self isEmpty] || props.isFlat){ - props.isFlat = YES; - } -} -- (void)swizzle_appendPath:(UIBezierPath *)bezierPath{ - UIBezierPathProperties* props = [self pathProperties]; - props.lastAddedElementWasMoveTo = NO; - UIBezierPathProperties* bezierPathProps = [bezierPath pathProperties]; - props.bezierPathByFlatteningPath = nil; - if(([self isEmpty] && bezierPathProps.isFlat) || (props.isFlat && bezierPathProps.isFlat)){ - props.isFlat = YES; - }else{ - props.isFlat = NO; - } - [self swizzle_appendPath:bezierPath]; - props.hasLastPoint = bezierPathProps.hasLastPoint; - props.lastPoint = bezierPathProps.lastPoint; - props.tangentAtEnd = bezierPathProps.tangentAtEnd; - props.cachedElementCount = 0; -} --(UIBezierPath*) swizzle_copy{ - UIBezierPathProperties* props = [self pathProperties]; - UIBezierPath* ret = [self swizzle_copy]; - CGMutablePathRef pathRef = CGPathCreateMutableCopy(self.CGPath); - ret.CGPath = pathRef; - CGPathRelease(pathRef); - UIBezierPathProperties* retProps = [ret pathProperties]; - retProps.lastAddedElementWasMoveTo = props.lastAddedElementWasMoveTo; - retProps.isFlat = props.isFlat; - retProps.hasLastPoint = props.hasLastPoint; - retProps.lastPoint = props.lastPoint; - retProps.hasFirstPoint = props.hasFirstPoint; - retProps.firstPoint = props.firstPoint; - retProps.tangentAtEnd = props.tangentAtEnd; - retProps.cachedElementCount = props.cachedElementCount; - retProps.isClosed = props.isClosed; - return ret; -} - -+(UIBezierPath*) swizzle_bezierPathWithRect:(CGRect)rect{ - UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRect:rect]; - UIBezierPathProperties* props = [path pathProperties]; - props.isFlat = YES; - props.knowsIfClosed = YES; - props.isClosed = YES; - return path; -} - -+(UIBezierPath*) swizzle_bezierPathWithOvalInRect:(CGRect)rect{ - UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithOvalInRect:rect]; - UIBezierPathProperties* props = [path pathProperties]; - props.isFlat = YES; - props.knowsIfClosed = YES; - props.isClosed = YES; - return path; -} - -+(UIBezierPath*) swizzle_bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii{ - UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:cornerRadii]; - UIBezierPathProperties* props = [path pathProperties]; - props.isFlat = YES; - props.knowsIfClosed = YES; - props.isClosed = YES; - return path; -} - -+(UIBezierPath*) swizzle_bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadii{ - UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRoundedRect:rect cornerRadius:cornerRadii]; - UIBezierPathProperties* props = [path pathProperties]; - props.isFlat = YES; - props.knowsIfClosed = YES; - props.isClosed = YES; - return path; -} - - - -+(void)load{ - @autoreleasepool { - NSError *error = nil; - [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRect:) - withClassMethod:@selector(swizzle_bezierPathWithRect:) - error:&error]; - [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithOvalInRect:) - withClassMethod:@selector(swizzle_bezierPathWithOvalInRect:) - error:&error]; - [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:) - withClassMethod:@selector(swizzle_bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:) - error:&error]; - [UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRoundedRect:cornerRadius:) - withClassMethod:@selector(swizzle_bezierPathWithRoundedRect:cornerRadius:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(moveToPoint:) - withMethod:@selector(swizzle_moveToPoint:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(addLineToPoint:) - withMethod:@selector(swizzle_addLineToPoint:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(addCurveToPoint:controlPoint1:controlPoint2:) - withMethod:@selector(swizzle_addCurveToPoint:controlPoint1:controlPoint2:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(addQuadCurveToPoint:controlPoint:) - withMethod:@selector(swizzle_quadCurveToPoint:controlPoint:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(closePath) - withMethod:@selector(swizzle_closePath) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(addArcWithCenter:radius:startAngle:endAngle:clockwise:) - withMethod:@selector(swizzle_arcWithCenter:radius:startAngle:endAngle:clockwise:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(removeAllPoints) - withMethod:@selector(swizzle_removeAllPoints) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(appendPath:) - withMethod:@selector(swizzle_appendPath:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(copy) - withMethod:@selector(swizzle_copy) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(initWithCoder:) - withMethod:@selector(swizzle_initWithCoder:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(encodeWithCoder:) - withMethod:@selector(swizzle_encodeWithCoder:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(applyTransform:) - withMethod:@selector(ahmed_swizzle_applyTransform:) - error:&error]; - [UIBezierPath jr_swizzleMethod:@selector(dealloc) - withMethod:@selector(ahmed_swizzle_dealloc) - error:&error]; - } -} - - - -/** - * if a curve, the ctrlpoint should be point1, and end point is point2 - * if line, prev point is point1, and end point is point2 - */ -- (CGFloat) calculateTangentBetween:(CGPoint)point1 andPoint:(CGPoint)point2{ - return atan2f( point1.y - point2.y, point1.x - point2.x ); -} - - - -/** - * calculate the point on a bezier at time t - * where 0 < t < 1 - */ -CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t) -{ - CGPoint q; - CGFloat mt = 1 - t; - - CGPoint bez1[4]; - CGPoint bez2[4]; - - q.x = mt * bez[1].x + t * bez[2].x; - q.y = mt * bez[1].y + t * bez[2].y; - bez1[1].x = mt * bez[0].x + t * bez[1].x; - bez1[1].y = mt * bez[0].y + t * bez[1].y; - bez2[2].x = mt * bez[2].x + t * bez[3].x; - bez2[2].y = mt * bez[2].y + t * bez[3].y; - - bez1[2].x = mt * bez1[1].x + t * q.x; - bez1[2].y = mt * bez1[1].y + t * q.y; - bez2[1].x = mt * q.x + t * bez2[2].x; - bez2[1].y = mt * q.y + t * bez2[2].y; - - bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x; - bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y; - - return CGPointMake(bez1[3].x, bez1[3].y); -} - -@end - diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h deleted file mode 100644 index aaa7d3489..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Performance_Private.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// UIBezierPath+Performance_Private.h -// PerformanceBezier -// -// Created by Adam Wulf on 10/16/12. -// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. -// - -#ifndef PerformanceBezier_UIBezierPath_Performance_Private_h -#define PerformanceBezier_UIBezierPath_Performance_Private_h - -#import "UIBezierPathProperties.h" - -@interface UIBezierPath (Performance_Private) - -// helper functions for finding points and tangents -// on a bezier curve -CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t); -CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t); -CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d); -CGFloat dotProduct(const CGPoint p1, const CGPoint p2); - -@end - - -#endif diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h deleted file mode 100644 index b667e35cb..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// UIBezierPath+Ahmed.h -// PerformanceBezier -// -// Created by Adam Wulf on 10/6/12. -// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. -// -// -// -// -// This category is motivated by the masters thesis of Athar Ahmad -// available at http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf -// -// More information available at -// http://www.cis.usouthal.edu/~hain/general/Thesis.htm -// -// subdivide code license in included SubdiviceLicense file - -#import -#include - - - -@interface UIBezierPath (Trim) - --(UIBezierPath*) bezierPathByTrimmingElement:(NSInteger)elementIndex fromTValue:(double)fromTValue toTValue:(double)toTValue; --(UIBezierPath*) bezierPathByTrimmingToElement:(NSInteger)elementIndex andTValue:(double)tValue; --(UIBezierPath*) bezierPathByTrimmingFromElement:(NSInteger)elementIndex andTValue:(double)tValue; - -+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atT:(CGFloat)t; - -+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atLength:(CGFloat)length withAcceptableError:(CGFloat)acceptableError withCache:(CGFloat*) subBezierlengthCache; - - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m deleted file mode 100644 index c397993d9..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m +++ /dev/null @@ -1,209 +0,0 @@ -// -// UIBezierPath+Trim.m -// PerformanceBezier -// -// Created by Adam Wulf on 10/6/12. -// Copyright (c) 2012 Milestone Made, LLC. All rights reserved. -// - -#import "UIBezierPath+Trim.h" -#import -#import - -@implementation UIBezierPath (Trim) - -/** - * this will trim a specific element from a tvalue to a tvalue - */ --(UIBezierPath*) bezierPathByTrimmingElement:(NSInteger)elementIndex fromTValue:(double)fromTValue toTValue:(double)toTValue{ - __block CGPoint previousEndpoint; - __block UIBezierPath* outputPath = [UIBezierPath bezierPath]; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ - if(currentIndex < elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - previousEndpoint = element.points[2]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - previousEndpoint = element.points[0]; - } - }else if(currentIndex == elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - [outputPath moveToPoint:element.points[0]]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - CGPoint bez[4]; - bez[0] = previousEndpoint; - bez[1] = element.points[0]; - bez[2] = element.points[1]; - bez[3] = element.points[2]; - - previousEndpoint = element.points[2]; - - CGPoint left[4], right[4]; - subdivideBezierAtT(bez, left, right, toTValue); - bez[0] = left[0]; - bez[1] = left[1]; - bez[2] = left[2]; - bez[3] = left[3]; - subdivideBezierAtT(bez, left, right, fromTValue / toTValue); - [outputPath moveToPoint:right[0]]; - [outputPath addCurveToPoint:right[3] controlPoint1:right[1] controlPoint2:right[2]]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - CGPoint startPoint = CGPointMake(previousEndpoint.x + fromTValue * (element.points[0].x - previousEndpoint.x), - previousEndpoint.y + fromTValue * (element.points[0].y - previousEndpoint.y)); - CGPoint endPoint = CGPointMake(previousEndpoint.x + toTValue * (element.points[0].x - previousEndpoint.x), - previousEndpoint.y + toTValue * (element.points[0].y - previousEndpoint.y)); - previousEndpoint = element.points[0]; - [outputPath moveToPoint:startPoint]; - [outputPath addLineToPoint:endPoint]; - } - } - }]; - - return outputPath; -} - - - - -/** - * this will trim a uibezier path from the input element index - * and that element's tvalue. it will return all elements after - * that input - */ --(UIBezierPath*) bezierPathByTrimmingFromElement:(NSInteger)elementIndex andTValue:(double)tValue{ - __block CGPoint previousEndpoint; - __block UIBezierPath* outputPath = [UIBezierPath bezierPath]; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ - if(currentIndex < elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - previousEndpoint = element.points[2]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - previousEndpoint = element.points[0]; - } - }else if(currentIndex == elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - [outputPath moveToPoint:element.points[0]]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - CGPoint bez[4]; - bez[0] = previousEndpoint; - bez[1] = element.points[0]; - bez[2] = element.points[1]; - bez[3] = element.points[2]; - - previousEndpoint = element.points[2]; - - CGPoint left[4], right[4]; - subdivideBezierAtT(bez, left, right, tValue); - [outputPath moveToPoint:right[0]]; - [outputPath addCurveToPoint:right[3] controlPoint1:right[1] controlPoint2:right[2]]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - CGPoint startPoint = CGPointMake(previousEndpoint.x + tValue * (element.points[0].x - previousEndpoint.x), - previousEndpoint.y + tValue * (element.points[0].y - previousEndpoint.y)); - previousEndpoint = element.points[0]; - [outputPath moveToPoint:startPoint]; - [outputPath addLineToPoint:element.points[0]]; - } - }else if(currentIndex > elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - [outputPath moveToPoint:element.points[0]]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - previousEndpoint = element.points[2]; - [outputPath addCurveToPoint:element.points[2] controlPoint1:element.points[0] controlPoint2:element.points[1]]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - previousEndpoint = element.points[0]; - [outputPath addLineToPoint:element.points[0]]; - } - } - }]; - - return outputPath; -} - -/** - * this will trim a uibezier path to the input element index - * and that element's tvalue. it will return all elements before - * that input - */ --(UIBezierPath*) bezierPathByTrimmingToElement:(NSInteger)elementIndex andTValue:(double)tValue{ - __block CGPoint previousEndpoint; - __block UIBezierPath* outputPath = [UIBezierPath bezierPath]; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){ - if(currentIndex == elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - [outputPath moveToPoint:element.points[0]]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - CGPoint bez[4]; - bez[0] = previousEndpoint; - bez[1] = element.points[0]; - bez[2] = element.points[1]; - bez[3] = element.points[2]; - - previousEndpoint = element.points[2]; - - CGPoint left[4], right[4]; - subdivideBezierAtT(bez, left, right, tValue); - [outputPath addCurveToPoint:left[3] controlPoint1:left[1] controlPoint2:left[2]]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - CGPoint endPoint = CGPointMake(previousEndpoint.x + tValue * (element.points[0].x - previousEndpoint.x), - previousEndpoint.y + tValue * (element.points[0].y - previousEndpoint.y)); - previousEndpoint = element.points[0]; - [outputPath addLineToPoint:endPoint]; - } - }else if(currentIndex < elementIndex){ - if(element.type == kCGPathElementMoveToPoint){ - // moveto - previousEndpoint = element.points[0]; - [outputPath moveToPoint:element.points[0]]; - }else if(element.type == kCGPathElementAddCurveToPoint ){ - // curve - previousEndpoint = element.points[2]; - [outputPath addCurveToPoint:element.points[2] controlPoint1:element.points[0] controlPoint2:element.points[1]]; - }else if(element.type == kCGPathElementAddLineToPoint){ - // line - previousEndpoint = element.points[0]; - [outputPath addLineToPoint:element.points[0]]; - } - } - }]; - - return outputPath; -} - -+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atT:(CGFloat)t{ - subdivideBezierAtT(bez, bez1, bez2, t); -} - -+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2{ - subdivideBezierAtT(bez, bez1, bez2, .5); -} - -+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atLength:(CGFloat)length withAcceptableError:(CGFloat)acceptableError withCache:(CGFloat*) subBezierlengthCache{ - subdivideBezierAtLengthWithCache(bez, bez1, bez2, length, acceptableError,subBezierlengthCache); -} - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h deleted file mode 100644 index 323ad9ccb..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// UIBezierPath+DKFix.h -// ClippingBezier -// -// Created by Adam Wulf on 5/9/15. -// -// - -#import - -@interface UIBezierPath (Trimming) - --(void) appendPathRemovingInitialMoveToPoint:(UIBezierPath*)otherPath; - --(NSArray*) subPaths; - --(NSInteger) countSubPaths; - -- (NSInteger) subpathIndexForElement:(NSInteger) element; - -- (CGFloat) length; - -- (CGFloat) tangentAtStart; - -- (CGFloat) tangentAtStartOfSubpath:(NSInteger)index; - -- (UIBezierPath*) bezierPathByTrimmingFromLength:(CGFloat)trimLength; - -- (UIBezierPath*) bezierPathByTrimmingToLength:(CGFloat)trimLength; - -- (UIBezierPath*) bezierPathByTrimmingToLength:(CGFloat)trimLength withMaximumError:(CGFloat)err; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m deleted file mode 100644 index 0b474c00a..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m +++ /dev/null @@ -1,365 +0,0 @@ -// -// UIBezierPath+DKFix.m -// ClippingBezier -// -// Created by Adam Wulf on 5/9/15. -// -// - -#import "UIBezierPath+Trimming.h" -#import -#pragma mark - Subdivide helpers by Alastair J. Houghton -/* - * Bezier path utility category (trimming) - * - * (c) 2004 Alastair J. Houghton - * All Rights Reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. The name of the author of this software may not be used to endorse - * or promote products derived from the software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -@implementation UIBezierPath (Trimming) - --(NSInteger) countSubPaths{ - return [[self subPaths] count]; -} - -/* Return an NSBezierPath corresponding to the first trimLength units - of this NSBezierPath. */ -- (UIBezierPath *)bezierPathByTrimmingToLength:(CGFloat)trimLength - withMaximumError:(CGFloat)maxError -{ - UIBezierPath *newPath = [UIBezierPath bezierPath]; - NSInteger elements = [self elementCount]; - int n; - double length = 0.0; - CGPoint pointForClose = CGPointMake(0.0, 0.0); - CGPoint lastPoint = CGPointMake (0.0, 0.0); - - for (n = 0; n < elements; ++n) { - CGPoint points[3]; - CGPathElement element = [self elementAtIndex:n - associatedPoints:points]; - double elementLength; - double remainingLength = trimLength - length; - - if(remainingLength == 0){ - break; - } - - switch (element.type) { - case kCGPathElementMoveToPoint: - [newPath moveToPoint:points[0]]; - pointForClose = lastPoint = points[0]; - continue; - - case kCGPathElementAddLineToPoint: - elementLength = distance (lastPoint, points[0]); - - if (length + elementLength <= trimLength) - [newPath addLineToPoint:points[0]]; - else { - double f = remainingLength / elementLength; - [newPath addLineToPoint:CGPointMake (lastPoint.x - + f * (points[0].x - lastPoint.x), - lastPoint.y - + f * (points[0].y - lastPoint.y))]; - return newPath; - } - - length += elementLength; - lastPoint = points[0]; - break; - - case kCGPathElementAddCurveToPoint: - case kCGPathElementAddQuadCurveToPoint: { - CGPoint bezier[4]; - if(element.type == kCGPathElementAddCurveToPoint){ - bezier[0] = lastPoint; - bezier[1] = points[0]; - bezier[2] = points[1]; - bezier[3] = points[2]; - }else{ - bezier[0] = lastPoint; - bezier[1] = points[0]; - bezier[2] = points[0]; - bezier[3] = points[1]; - } - elementLength = lengthOfBezier (bezier, maxError); - - if (length + elementLength <= trimLength) - [newPath addCurveToPoint:points[2] controlPoint1:points[0] controlPoint2:points[1]]; - else { - CGPoint bez1[4], bez2[4]; - subdivideBezierAtLength (bezier, bez1, bez2, - remainingLength, maxError); - [newPath addCurveToPoint:bez1[3] controlPoint1:bez1[1] controlPoint2:bez1[2]]; - return newPath; - } - - length += elementLength; - lastPoint = points[2]; - break; - } - - case kCGPathElementCloseSubpath: - elementLength = distance (lastPoint, pointForClose); - - if (length + elementLength <= trimLength) - [newPath closePath]; - else { - double f = remainingLength / elementLength; - [newPath addLineToPoint:CGPointMake(lastPoint.x - + f * (points[0].x - lastPoint.x), - lastPoint.y - + f * (points[0].y - lastPoint.y))]; - return newPath; - } - - length += elementLength; - lastPoint = pointForClose; - break; - } - } - - return newPath; -} - -// Convenience method -- (UIBezierPath *)bezierPathByTrimmingToLength:(CGFloat)trimLength -{ - return [self bezierPathByTrimmingToLength:trimLength withMaximumError:0.1]; -} - -/* Return an NSBezierPath corresponding to the part *after* the first - trimLength units of this NSBezierPath. */ -- (UIBezierPath *)bezierPathByTrimmingFromLength:(CGFloat)trimLength - withMaximumError:(CGFloat)maxError -{ - UIBezierPath *newPath = [UIBezierPath bezierPath]; - NSInteger elements = [self elementCount]; - int n; - double length = 0.0; - CGPoint pointForClose = CGPointMake (0.0, 0.0); - CGPoint lastPoint = CGPointMake (0.0, 0.0); - BOOL legitMoveTo = NO; - - for (n = 0; n < elements; ++n) { - CGPoint points[3]; - CGPathElement element = [self elementAtIndex:n - associatedPoints:points]; - double elementLength; - double remainingLength = trimLength - length; - - switch (element.type) { - case kCGPathElementMoveToPoint: - if(remainingLength < 0){ - [newPath moveToPoint:points[0]]; - legitMoveTo = YES; - } - pointForClose = lastPoint = points[0]; - continue; - - case kCGPathElementAddLineToPoint: - elementLength = distance (lastPoint, points[0]); - - if (length > trimLength){ - [newPath addLineToPoint:points[0]]; - }else if (length + elementLength > trimLength) { - double f = remainingLength / elementLength; - [newPath moveToPoint:CGPointMake (lastPoint.x - + f * (points[0].x - lastPoint.x), - lastPoint.y - + f * (points[0].y - lastPoint.y))]; - [newPath addLineToPoint:points[0]]; - } - - length += elementLength; - lastPoint = points[0]; - break; - - case kCGPathElementAddCurveToPoint: - case kCGPathElementAddQuadCurveToPoint: { - CGPoint bezier[4]; - if(element.type == kCGPathElementAddCurveToPoint){ - bezier[0] = lastPoint; - bezier[1] = points[0]; - bezier[2] = points[1]; - bezier[3] = points[2]; - }else{ - bezier[0] = lastPoint; - bezier[1] = points[0]; - bezier[2] = points[0]; - bezier[3] = points[1]; - } - elementLength = lengthOfBezier (bezier, maxError); - - if (length > trimLength){ - [newPath addCurveToPoint:points[2] - controlPoint1:points[0] - controlPoint2:points[1]]; - }else if (length + elementLength > trimLength) { - CGPoint bez1[4], bez2[4]; - subdivideBezierAtLength (bezier, bez1, bez2, - remainingLength, maxError); - [newPath moveToPoint:bez2[0]]; - [newPath addCurveToPoint:bez2[3] - controlPoint1:bez2[1] - controlPoint2:bez2[2]]; - } - - length += elementLength; - lastPoint = points[2]; - break; - } - - case kCGPathElementCloseSubpath: - elementLength = distance (lastPoint, pointForClose); - - if (length > trimLength){ - if(legitMoveTo){ - [newPath closePath]; - }else{ - [newPath addLineToPoint:pointForClose]; - } - } else if (length + elementLength > trimLength) { - double f = remainingLength / elementLength; - [newPath moveToPoint:CGPointMake (lastPoint.x - + f * (points[0].x - lastPoint.x), - lastPoint.y - + f * (points[0].y - lastPoint.y))]; - [newPath addLineToPoint:points[0]]; - } - - length += elementLength; - lastPoint = pointForClose; - break; - } - } - - return newPath; -} - -// Convenience method -- (UIBezierPath *)bezierPathByTrimmingFromLength:(CGFloat)trimLength -{ - return [self bezierPathByTrimmingFromLength:trimLength withMaximumError:0.1]; -} - -- (NSInteger) subpathIndexForElement:(NSInteger) element{ - __block NSInteger subpathIndex = -1; - __block BOOL lastWasMoveTo = NO; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { - if(element.type == kCGPathElementMoveToPoint){ - if(!lastWasMoveTo){ - subpathIndex += 1; - } - lastWasMoveTo = YES; - }else{ - lastWasMoveTo = NO; - } - }]; - return subpathIndex; -} - -- (CGFloat) length{ - __block CGFloat length = 0; - __block CGPoint lastMoveToPoint = CGPointNotFound; - __block CGPoint lastElementEndPoint = CGPointNotFound; - [self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) { - if(element.type == kCGPathElementMoveToPoint){ - lastElementEndPoint = element.points[0]; - lastMoveToPoint = element.points[0]; - }else if(element.type == kCGPathElementCloseSubpath){ - length += distance(lastElementEndPoint, lastMoveToPoint); - lastElementEndPoint = lastMoveToPoint; - }else if(element.type == kCGPathElementAddLineToPoint){ - length += distance(lastElementEndPoint, element.points[0]); - lastElementEndPoint = element.points[0]; - }else if(element.type == kCGPathElementAddQuadCurveToPoint || - element.type == kCGPathElementAddCurveToPoint){ - - CGPoint bez[4]; - bez[0] = lastElementEndPoint; - - if(element.type == kCGPathElementAddQuadCurveToPoint){ - bez[1] = element.points[0]; - bez[2] = element.points[0]; - bez[3] = element.points[1]; - lastElementEndPoint = element.points[1]; - }else if(element.type == kCGPathElementAddCurveToPoint){ - bez[1] = element.points[0]; - bez[2] = element.points[1]; - bez[3] = element.points[2]; - lastElementEndPoint = element.points[2]; - } - - length += lengthOfBezier(bez, .5);; - } - }]; - return length; -} - -- (CGFloat) tangentAtStart{ - if([self elementCount] < 2){ - return 0.0; - } - - CGPathElement ele1 = [self elementAtIndex:0]; - CGPathElement ele2 = [self elementAtIndex:1]; - - if(ele1.type != kCGPathElementMoveToPoint){ - return 0.0; - } - - CGPoint point1 = ele1.points[0]; - CGPoint point2 = CGPointZero; - - switch (ele2.type) { - case kCGPathElementMoveToPoint: - return 0.0; - break; - case kCGPathElementAddCurveToPoint: - case kCGPathElementAddQuadCurveToPoint: - case kCGPathElementAddLineToPoint: - point2 = ele2.points[0]; - break; - case kCGPathElementCloseSubpath: - return 0.0; - break; - } - - return atan2f( point2.y - point1.y, point2.x - point1.x ) + M_PI; -} - -- (CGFloat) tangentAtStartOfSubpath:(NSInteger)index{ - return [[[self subPaths] objectAtIndex:index] tangentAtStart]; -} - - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h deleted file mode 100644 index 0ba08f331..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// UIBezierPath+Uncached.h -// PerformanceBezier -// -// Created by Adam Wulf on 2/6/15. -// -// - -#import - -@interface UIBezierPath (Uncached) - -#ifdef MMPreventBezierPerformance --(void) simulateNoBezierCaching; -#endif - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m deleted file mode 100644 index 791bfc51e..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Uncached.m +++ /dev/null @@ -1,23 +0,0 @@ -// -// UIBezierPath+Uncached.m -// PerformanceBezier -// -// Created by Adam Wulf on 2/6/15. -// -// - -#import "UIBezierPath+Uncached.h" -#import "UIBezierPath+NSOSX.h" - -@implementation UIBezierPath (Uncached) - - -#ifdef MMPreventBezierPerformance --(void) simulateNoBezierCaching{ - [self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){ - // noop - }]; -} -#endif - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h deleted file mode 100644 index b7b1017b4..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// UIBezierPath+Util.h -// PerformanceBezier -// -// Created by Adam Wulf on 5/20/15. -// -// - -#import - -@interface UIBezierPath (Util) - -+(CGFloat) lengthOfBezier:(const CGPoint[4])bez withAccuracy:(CGFloat)accuracy; - -@end - -#if defined __cplusplus -extern "C" { -#endif - - // simple helper function to return the distance of a point to a line - CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end); - - // returns the distance between two points - CGFloat distance(const CGPoint p1, const CGPoint p2); - - void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t); - - CGFloat subdivideBezierAtLength (const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat length, CGFloat acceptableError); - - CGFloat subdivideBezierAtLengthWithCache(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat length, CGFloat acceptableError, - CGFloat* subBezierLengthCache); - - CGFloat lengthOfBezier(const CGPoint bez[4], CGFloat acceptableError); - - - CGPoint lineSegmentIntersection(CGPoint A, CGPoint B, CGPoint C, CGPoint D); - - CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t); - - CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d); - - - -#if defined __cplusplus -}; -#endif - diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m deleted file mode 100644 index 5688f8a82..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m +++ /dev/null @@ -1,316 +0,0 @@ -// -// UIBezierPath+Util.m -// PerformanceBezier -// -// Created by Adam Wulf on 5/20/15. -// -// - -#import "PerformanceBezier.h" -#import "UIBezierPath+Util.h" -#import "UIBezierPath+Trim.h" -#import "UIBezierPath+Performance.h" - -@implementation UIBezierPath (Util) - -+(CGFloat) lengthOfBezier:(const CGPoint[4])bez withAccuracy:(CGFloat)accuracy{ - return lengthOfBezier(bez, accuracy); -} - -#pragma mark - Subdivide helpers by Alastair J. Houghton -/* - * Bezier path utility category (trimming) - * - * (c) 2004 Alastair J. Houghton - * All Rights Reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. The name of the author of this software may not be used to endorse - * or promote products derived from the software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -// Subdivide a Bézier (50% subdivision) -inline static void subdivideBezier(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4]) -{ - CGPoint q; - - bez1[0].x = bez[0].x; - bez1[0].y = bez[0].y; - bez2[3].x = bez[3].x; - bez2[3].y = bez[3].y; - - q.x = (bez[1].x + bez[2].x) / 2.0; - q.y = (bez[1].y + bez[2].y) / 2.0; - bez1[1].x = (bez[0].x + bez[1].x) / 2.0; - bez1[1].y = (bez[0].y + bez[1].y) / 2.0; - bez2[2].x = (bez[2].x + bez[3].x) / 2.0; - bez2[2].y = (bez[2].y + bez[3].y) / 2.0; - - bez1[2].x = (bez1[1].x + q.x) / 2.0; - bez1[2].y = (bez1[1].y + q.y) / 2.0; - bez2[1].x = (q.x + bez2[2].x) / 2.0; - bez2[1].y = (q.y + bez2[2].y) / 2.0; - - bez1[3].x = bez2[0].x = (bez1[2].x + bez2[1].x) / 2.0; - bez1[3].y = bez2[0].y = (bez1[2].y + bez2[1].y) / 2.0; -} - -// Subdivide a Bézier (specific division) -void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t) -{ - CGPoint q; - CGFloat mt = 1 - t; - - bez1[0].x = bez[0].x; - bez1[0].y = bez[0].y; - bez2[3].x = bez[3].x; - bez2[3].y = bez[3].y; - - q.x = mt * bez[1].x + t * bez[2].x; - q.y = mt * bez[1].y + t * bez[2].y; - bez1[1].x = mt * bez[0].x + t * bez[1].x; - bez1[1].y = mt * bez[0].y + t * bez[1].y; - bez2[2].x = mt * bez[2].x + t * bez[3].x; - bez2[2].y = mt * bez[2].y + t * bez[3].y; - - bez1[2].x = mt * bez1[1].x + t * q.x; - bez1[2].y = mt * bez1[1].y + t * q.y; - bez2[1].x = mt * q.x + t * bez2[2].x; - bez2[1].y = mt * q.y + t * bez2[2].y; - - bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x; - bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y; -} - - -// Length of a curve -CGFloat lengthOfBezier(const CGPoint bez[4], CGFloat acceptableError) -{ - CGFloat polyLen = 0.0; - CGFloat chordLen = distance(bez[0], bez[3]); - CGFloat retLen, errLen; - NSUInteger n; - - for (n = 0; n < 3; ++n) - polyLen += distance(bez[n], bez[n + 1]); - - errLen = polyLen - chordLen; - - if (errLen > acceptableError) { - CGPoint left[4], right[4]; - subdivideBezier (bez, left, right); - retLen = (lengthOfBezier (left, acceptableError) - + lengthOfBezier (right, acceptableError)); - } else { - retLen = 0.5 * (polyLen + chordLen); - } - - return retLen; -} - -// Split a Bézier curve at a specific length -CGFloat subdivideBezierAtLength (const CGPoint bez[4], - CGPoint bez1[4], - CGPoint bez2[4], - CGFloat length, - CGFloat acceptableError) -{ - return subdivideBezierAtLengthWithCache(bez, bez1, bez2, length, acceptableError, NULL); -} - -/** - * will split the input bezier curve at the input length - * within a given margin of error - * - * the two curves will exactly match the original curve - */ -CGFloat subdivideBezierAtLengthWithCache(const CGPoint bez[4], - CGPoint bez1[4], - CGPoint bez2[4], - CGFloat length, - CGFloat acceptableError, - CGFloat* subBezierLengthCache){ - CGFloat top = 1.0, bottom = 0.0; - CGFloat t, prevT; - BOOL needsDealloc = NO; - - if(!subBezierLengthCache){ - subBezierLengthCache = calloc(1000, sizeof(CGFloat)); - needsDealloc = YES; - } - - prevT = t = 0.5; - for (;;) { - CGFloat len1; - - subdivideBezierAtT (bez, bez1, bez2, t); - - int lengthCacheIndex = (int)floorf(t*1000); - len1 = subBezierLengthCache[lengthCacheIndex]; - if(!len1){ - len1 = [UIBezierPath lengthOfBezier:bez1 withAccuracy:0.5 * acceptableError]; - subBezierLengthCache[lengthCacheIndex] = len1; - } - - if (fabs (length - len1) < acceptableError){ - return len1; - } - - if (length > len1) { - bottom = t; - t = 0.5 * (t + top); - } else if (length < len1) { - top = t; - t = 0.5 * (bottom + t); - } - - if (t == prevT){ - subBezierLengthCache[lengthCacheIndex] = len1; - - if(needsDealloc){ - free(subBezierLengthCache); - } - return len1; - } - - prevT = t; - } -} - - - -// public domain function by Darel Rex Finley, 2006 - -// Determines the intersection point of the line segment defined by points A and B -// with the line segment defined by points C and D. -// -// Returns YES if the intersection point was found, and stores that point in X,Y. -// Returns NO if there is no determinable intersection point, in which case X,Y will -// be unmodified. - -CGPoint lineSegmentIntersection(CGPoint A, CGPoint B, CGPoint C, CGPoint D) { - - double distAB, theCos, theSin, newX, ABpos ; - - // Fail if either line segment is zero-length. - if ((A.x==B.x && A.y==B.y) || (C.x==D.x && C.y==D.y)) return CGPointNotFound; - - // Fail if the segments share an end-point. - if ((A.x==C.x && A.y==C.y) || - (B.x==C.x && B.y==C.y) || - (A.x==D.x && A.y==D.y) || - (B.x==D.x && B.y==D.y)) { - return CGPointNotFound; - } - - // (1) Translate the system so that point A is on the origin. - B.x-=A.x; B.y-=A.y; - C.x-=A.x; C.y-=A.y; - D.x-=A.x; D.y-=A.y; - - // Discover the length of segment A-B. - distAB=sqrt(B.x*B.x+B.y*B.y); - - // (2) Rotate the system so that point B is on the positive X axis. - theCos=B.x/distAB; - theSin=B.y/distAB; - newX=C.x*theCos+C.y*theSin; - C.y =C.y*theCos-C.x*theSin; - C.x=newX; - newX=D.x*theCos+D.y*theSin; - D.y =D.y*theCos-D.x*theSin; - D.x=newX; - - // Fail if segment C-D doesn't cross line A-B. - if ((C.y<0. && D.y<0.) || (C.y>=0. && D.y>=0.)) return CGPointNotFound; - - // (3) Discover the position of the intersection point along line A-B. - ABpos=D.x+(C.x-D.x)*D.y/(D.y-C.y); - - // Fail if segment C-D crosses line A-B outside of segment A-B. - if (ABpos<0. || ABpos>distAB) return CGPointNotFound; - - // (4) Apply the discovered position to line A-B in the original coordinate system. - // Success. - return CGPointMake(A.x+ABpos*theCos, A.y+ABpos*theSin); -} - - -#pragma mark - Helper - - -// primary algorithm from: -// http://stackoverflow.com/questions/4089443/find-the-tangent-of-a-point-on-a-cubic-bezier-curve-on-an-iphone -CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t) -{ - return CGPointMake(bezierTangent(t, bez[0].x, bez[1].x, bez[2].x, bez[3].x), - bezierTangent(t, bez[0].y, bez[1].y, bez[2].y, bez[3].y)); -} -CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) -{ - CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a ); - CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) ); - CGFloat C3 = ( (3.0 * b) - (3.0 * a) ); - return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 ); -} - - -/** - * returns the shortest distance from a point to a line - */ -CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end){ - CGPoint v = CGPointMake(end.x - start.x, end.y - start.y); - CGPoint w = CGPointMake(point.x - start.x, point.y - start.y); - CGFloat c1 = dotProduct(w, v); - CGFloat c2 = dotProduct(v, v); - CGFloat d; - if (c1 <= 0) { - d = distance(point, start); - } - else if (c2 <= c1) { - d = distance(point, end); - } - else { - CGFloat b = c1 / c2; - CGPoint Pb = CGPointMake(start.x + b * v.x, start.y + b * v.y); - d = distance(point, Pb); - } - return d; -} -/** - * returns the distance between two points - */ -CGFloat distance(const CGPoint p1, const CGPoint p2) { - return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2)); -} -/** - * returns the dot product of two coordinates - */ -CGFloat dotProduct(const CGPoint p1, const CGPoint p2) { - return p1.x * p2.x + p1.y * p2.y; -} - - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h b/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h deleted file mode 100644 index be99d0630..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// UIBezierPathProperties.h -// PerformanceBezier -// -// Created by Adam Wulf on 2/1/15. -// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. -// - -#import -#import - -@interface UIBezierPathProperties : NSObject -@property (nonatomic) BOOL isClosed; -@property (nonatomic) BOOL knowsIfClosed; -@property (nonatomic) BOOL isFlat; -@property (nonatomic) BOOL hasLastPoint; -@property (nonatomic) CGPoint lastPoint; -@property (nonatomic) BOOL hasFirstPoint; -@property (nonatomic) CGPoint firstPoint; -@property (nonatomic) CGFloat tangentAtEnd; -@property (nonatomic) NSInteger cachedElementCount; -@property (nonatomic, retain) UIBezierPath* bezierPathByFlatteningPath; -@property (nonatomic) BOOL lastAddedElementWasMoveTo; - -@end diff --git a/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m b/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m deleted file mode 100644 index bb5e90416..000000000 --- a/ios/PerformanceBezier/PerformanceBezier/UIBezierPathProperties.m +++ /dev/null @@ -1,81 +0,0 @@ -// -// UIBezierPathProperties.m -// PerformanceBezier -// -// Created by Adam Wulf on 2/1/15. -// Copyright (c) 2015 Milestone Made, LLC. All rights reserved. -// - -#import "UIBezierPathProperties.h" - -@implementation UIBezierPathProperties{ - BOOL isFlat; - BOOL knowsIfClosed; - BOOL isClosed; - BOOL hasLastPoint; - CGPoint lastPoint; - BOOL hasFirstPoint; - CGPoint firstPoint; - CGFloat tangentAtEnd; - NSInteger cachedElementCount; - UIBezierPath* bezierPathByFlatteningPath; -} - -@synthesize isFlat; -@synthesize knowsIfClosed; -@synthesize isClosed; -@synthesize hasLastPoint; -@synthesize lastPoint; -@synthesize tangentAtEnd; -@synthesize cachedElementCount; -@synthesize bezierPathByFlatteningPath; -@synthesize hasFirstPoint; -@synthesize firstPoint; - -- (id)initWithCoder:(NSCoder *)decoder{ - self = [super init]; - if (!self) { - return nil; - } - isFlat = [decoder decodeBoolForKey:@"pathProperties_isFlat"]; - knowsIfClosed = [decoder decodeBoolForKey:@"pathProperties_knowsIfClosed"]; - isClosed = [decoder decodeBoolForKey:@"pathProperties_isClosed"]; - hasLastPoint = [decoder decodeBoolForKey:@"pathProperties_hasLastPoint"]; - lastPoint = [decoder decodeCGPointForKey:@"pathProperties_lastPoint"]; - hasFirstPoint = [decoder decodeBoolForKey:@"pathProperties_hasFirstPoint"]; - firstPoint = [decoder decodeCGPointForKey:@"pathProperties_firstPoint"]; - tangentAtEnd = [decoder decodeFloatForKey:@"pathProperties_tangentAtEnd"]; - cachedElementCount = [decoder decodeIntegerForKey:@"pathProperties_cachedElementCount"]; - return self; -} - --(void) encodeWithCoder:(NSCoder *)aCoder{ - [aCoder encodeBool:isFlat forKey:@"pathProperties_isFlat"]; - [aCoder encodeBool:knowsIfClosed forKey:@"pathProperties_knowsIfClosed"]; - [aCoder encodeBool:isClosed forKey:@"pathProperties_isClosed"]; - [aCoder encodeBool:hasLastPoint forKey:@"pathProperties_hasLastPoint"]; - [aCoder encodeCGPoint:lastPoint forKey:@"pathProperties_lastPoint"]; - [aCoder encodeBool:hasFirstPoint forKey:@"pathProperties_hasFirstPoint"]; - [aCoder encodeCGPoint:firstPoint forKey:@"pathProperties_firstPoint"]; - [aCoder encodeFloat:tangentAtEnd forKey:@"pathProperties_tangentAtEnd"]; - [aCoder encodeInteger:cachedElementCount forKey:@"pathProperties_cachedElementCount"]; -} - -// for some reason the iPad 1 on iOS 5 needs to have this -// method coded and not synthesized. --(void) setBezierPathByFlatteningPath:(UIBezierPath *)_bezierPathByFlatteningPath{ - if(bezierPathByFlatteningPath != _bezierPathByFlatteningPath){ - [bezierPathByFlatteningPath release]; - [_bezierPathByFlatteningPath retain]; - } - bezierPathByFlatteningPath = _bezierPathByFlatteningPath; -} - --(void) dealloc{ - [bezierPathByFlatteningPath release]; - bezierPathByFlatteningPath = nil; - [super dealloc]; -} - - -@end diff --git a/ios/PerformanceBezier/PerformanceBezierTests/Info.plist b/ios/PerformanceBezier/PerformanceBezierTests/Info.plist deleted file mode 100644 index ba72822e8..000000000 --- a/ios/PerformanceBezier/PerformanceBezierTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h deleted file mode 100644 index 775c30aca..000000000 --- a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// PerformanceBezierAbstractTest.h -// PerformanceBezier -// -// Created by Adam Wulf on 11/20/13. -// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. -// - -#import "PerformanceBezier.h" - -@interface PerformanceBezierAbstractTest : XCTestCase - -@property (nonatomic, readonly) UIBezierPath* complexShape; - --(CGFloat) round:(CGFloat)val to:(int)digits; - --(BOOL) point:(CGPoint)p1 isNearTo:(CGPoint)p2; - --(BOOL) checkTanPoint:(CGFloat) f1 isLessThan:(CGFloat)f2; --(BOOL) check:(CGFloat) f1 isLessThan:(CGFloat)f2 within:(CGFloat)marginOfError; - -@end - diff --git a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m deleted file mode 100644 index b71201211..000000000 --- a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierAbstractTest.m +++ /dev/null @@ -1,161 +0,0 @@ -// -// PerformanceBezierAbstractTest.m -// PerformanceBezier -// -// Created by Adam Wulf on 11/20/13. -// Copyright (c) 2013 Milestone Made, LLC. All rights reserved. -// - -#import -#import "PerformanceBezierAbstractTest.h" - -#define kIntersectionPointPrecision .1 - -@implementation PerformanceBezierAbstractTest{ - UIBezierPath* complexShape; -} - -@synthesize complexShape; - --(void) setUp{ - complexShape = [UIBezierPath bezierPath]; - [complexShape moveToPoint:CGPointMake(218.500000,376.000000)]; - [complexShape addCurveToPoint:CGPointMake(227.000000,362.000000) controlPoint1:CGPointMake(218.100235,369.321564) controlPoint2:CGPointMake(222.816589,365.945923)]; - // [complexShape addCurveToPoint:CGPointMake(213.000000,372.000000) controlPoint1:CGPointMake(237.447144,352.833008) controlPoint2:CGPointMake(255.082840,326.966705)]; - [complexShape addCurveToPoint:CGPointMake(243.000000,316.000000) controlPoint1:CGPointMake(232.801880,363.107697) controlPoint2:CGPointMake(248.582321,338.033752)]; - [complexShape addCurveToPoint:CGPointMake(172.000000,218.000000) controlPoint1:CGPointMake(215.326599,286.866608) controlPoint2:CGPointMake(182.880371,258.374298)]; - [complexShape addCurveToPoint:CGPointMake(242.000000,111.000000) controlPoint1:CGPointMake(149.812820,168.222122) controlPoint2:CGPointMake(195.466583,119.790573)]; - [complexShape addCurveToPoint:CGPointMake(265.000000,109.000000) controlPoint1:CGPointMake(249.474472,108.948883) controlPoint2:CGPointMake(257.298248,108.285248)]; - [complexShape addCurveToPoint:CGPointMake(302.000000,116.000000) controlPoint1:CGPointMake(277.539948,110.401459) controlPoint2:CGPointMake(291.623962,104.844376)]; - [complexShape addCurveToPoint:CGPointMake(310.000000,148.000000) controlPoint1:CGPointMake(310.890778,124.607719) controlPoint2:CGPointMake(310.189972,136.988098)]; - [complexShape addCurveToPoint:CGPointMake(302.000000,184.000000) controlPoint1:CGPointMake(310.722900,160.600952) controlPoint2:CGPointMake(305.109131,172.145187)]; - [complexShape addCurveToPoint:CGPointMake(295.000000,221.000000) controlPoint1:CGPointMake(299.082794,196.217377) controlPoint2:CGPointMake(297.024170,208.612000)]; - [complexShape addCurveToPoint:CGPointMake(304.000000,294.000000) controlPoint1:CGPointMake(297.367493,244.586227) controlPoint2:CGPointMake(286.870880,273.642609)]; - [complexShape addCurveToPoint:CGPointMake(343.000000,305.000000) controlPoint1:CGPointMake(315.167755,304.101868) controlPoint2:CGPointMake(329.526489,302.807587)]; - [complexShape addCurveToPoint:CGPointMake(395.000000,297.000000) controlPoint1:CGPointMake(361.147430,306.169556) controlPoint2:CGPointMake(378.948700,305.707855)]; - [complexShape addCurveToPoint:CGPointMake(413.000000,285.000000) controlPoint1:CGPointMake(401.931854,294.629944) controlPoint2:CGPointMake(408.147980,290.431854)]; - [complexShape addCurveToPoint:CGPointMake(435.000000,255.000000) controlPoint1:CGPointMake(423.555023,278.199219) controlPoint2:CGPointMake(433.437531,268.110229)]; - [complexShape addCurveToPoint:CGPointMake(424.000000,228.000000) controlPoint1:CGPointMake(436.063995,245.070923) controlPoint2:CGPointMake(432.549713,233.803619)]; - [complexShape addCurveToPoint:CGPointMake(402.000000,209.000000) controlPoint1:CGPointMake(416.571869,221.776123) controlPoint2:CGPointMake(409.428101,215.223877)]; - [complexShape addCurveToPoint:CGPointMake(385.000000,183.000000) controlPoint1:CGPointMake(392.239838,203.283615) controlPoint2:CGPointMake(390.379822,191.873459)]; - [complexShape addCurveToPoint:CGPointMake(385.000000,128.000000) controlPoint1:CGPointMake(378.582947,166.163315) controlPoint2:CGPointMake(374.234924,143.960587)]; - [complexShape addCurveToPoint:CGPointMake(472.000000,105.000000) controlPoint1:CGPointMake(405.864136,103.071175) controlPoint2:CGPointMake(442.110016,95.355499)]; - [complexShape addLineToPoint:CGPointMake(472.000000,105.000000)]; - [complexShape addLineToPoint:CGPointMake(488.000000,177.000000)]; - [complexShape addCurveToPoint:CGPointMake(534.000000,177.000000) controlPoint1:CGPointMake(498.942047,187.308975) controlPoint2:CGPointMake(522.349426,190.655228)]; - [complexShape addLineToPoint:CGPointMake(534.000000,177.000000)]; - [complexShape addLineToPoint:CGPointMake(611.000000,77.000000)]; - [complexShape addCurveToPoint:CGPointMake(700.000000,111.000000) controlPoint1:CGPointMake(643.743652,54.625603) controlPoint2:CGPointMake(684.106140,80.752876)]; - [complexShape addCurveToPoint:CGPointMake(704.000000,126.000000) controlPoint1:CGPointMake(703.098755,115.324257) controlPoint2:CGPointMake(704.460693,120.742104)]; - [complexShape addCurveToPoint:CGPointMake(700.000000,156.000000) controlPoint1:CGPointMake(703.210999,136.088333) controlPoint2:CGPointMake(706.451782,146.830612)]; - [complexShape addCurveToPoint:CGPointMake(681.000000,196.000000) controlPoint1:CGPointMake(695.262329,170.166794) controlPoint2:CGPointMake(684.152832,181.086166)]; - [complexShape addCurveToPoint:CGPointMake(655.000000,246.000000) controlPoint1:CGPointMake(672.227966,212.611191) controlPoint2:CGPointMake(663.084045,229.033600)]; - [complexShape addCurveToPoint:CGPointMake(636.000000,300.000000) controlPoint1:CGPointMake(649.778381,264.429260) controlPoint2:CGPointMake(639.110535,280.894592)]; - [complexShape addCurveToPoint:CGPointMake(637.000000,374.000000) controlPoint1:CGPointMake(633.264893,324.262054) controlPoint2:CGPointMake(629.540955,350.094421)]; - [complexShape addCurveToPoint:CGPointMake(692.000000,512.000000) controlPoint1:CGPointMake(666.332947,413.789734) controlPoint2:CGPointMake(699.608032,459.043060)]; - [complexShape addCurveToPoint:CGPointMake(683.000000,536.000000) controlPoint1:CGPointMake(688.852905,519.944031) controlPoint2:CGPointMake(685.852539,527.945801)]; - [complexShape addCurveToPoint:CGPointMake(661.000000,571.000000) controlPoint1:CGPointMake(677.198853,548.592712) controlPoint2:CGPointMake(668.260010,559.289062)]; - [complexShape addCurveToPoint:CGPointMake(610.000000,619.000000) controlPoint1:CGPointMake(648.250000,590.532715) controlPoint2:CGPointMake(631.222351,608.483521)]; - [complexShape addCurveToPoint:CGPointMake(585.000000,625.000000) controlPoint1:CGPointMake(602.312439,623.111694) controlPoint2:CGPointMake(593.647827,625.135620)]; - [complexShape addCurveToPoint:CGPointMake(542.000000,612.000000) controlPoint1:CGPointMake(569.838867,626.477356) controlPoint2:CGPointMake(551.172668,629.067810)]; - [complexShape addCurveToPoint:CGPointMake(530.000000,557.000000) controlPoint1:CGPointMake(525.270691,596.196289) controlPoint2:CGPointMake(531.172729,575.895203)]; - [complexShape addCurveToPoint:CGPointMake(539.000000,514.000000) controlPoint1:CGPointMake(528.119263,541.759460) controlPoint2:CGPointMake(537.465271,528.714905)]; - [complexShape addCurveToPoint:CGPointMake(557.000000,458.000000) controlPoint1:CGPointMake(546.810364,495.904419) controlPoint2:CGPointMake(549.387146,476.172821)]; - [complexShape addCurveToPoint:CGPointMake(577.000000,334.000000) controlPoint1:CGPointMake(570.620544,419.011993) controlPoint2:CGPointMake(584.251587,375.770599)]; - [complexShape addCurveToPoint:CGPointMake(552.000000,307.000000) controlPoint1:CGPointMake(572.811646,321.661407) controlPoint2:CGPointMake(563.442261,312.060883)]; - [complexShape addCurveToPoint:CGPointMake(505.000000,304.000000) controlPoint1:CGPointMake(537.517578,299.902161) controlPoint2:CGPointMake(520.555420,301.162994)]; - [complexShape addCurveToPoint:CGPointMake(417.000000,395.000000) controlPoint1:CGPointMake(471.423096,326.244507) controlPoint2:CGPointMake(426.598511,351.118713)]; - [complexShape addCurveToPoint:CGPointMake(412.000000,423.000000) controlPoint1:CGPointMake(413.398468,403.873810) controlPoint2:CGPointMake(411.718628,413.460388)]; - [complexShape addCurveToPoint:CGPointMake(418.000000,460.000000) controlPoint1:CGPointMake(412.695190,435.349243) controlPoint2:CGPointMake(409.621643,449.299377)]; - [complexShape addCurveToPoint:CGPointMake(445.000000,488.000000) controlPoint1:CGPointMake(425.380219,470.784973) controlPoint2:CGPointMake(435.315186,479.392761)]; - [complexShape addCurveToPoint:CGPointMake(469.000000,514.000000) controlPoint1:CGPointMake(453.446838,496.144928) controlPoint2:CGPointMake(463.737061,503.078857)]; - [complexShape addCurveToPoint:CGPointMake(476.000000,562.000000) controlPoint1:CGPointMake(477.951324,528.615112) controlPoint2:CGPointMake(475.631348,545.762817)]; - [complexShape addLineToPoint:CGPointMake(476.000000,562.000000)]; - [complexShape addLineToPoint:CGPointMake(384.000000,717.000000)]; - [complexShape addCurveToPoint:CGPointMake(382.000000,740.000000) controlPoint1:CGPointMake(381.948761,724.474304) controlPoint2:CGPointMake(381.285309,732.298340)]; - [complexShape addCurveToPoint:CGPointMake(394.000000,768.000000) controlPoint1:CGPointMake(373.999329,755.779053) controlPoint2:CGPointMake(424.795959,792.055847)]; - [complexShape addCurveToPoint:CGPointMake(466.000000,790.000000) controlPoint1:CGPointMake(413.033173,794.757141) controlPoint2:CGPointMake(440.293671,789.339783)]; - [complexShape addCurveToPoint:CGPointMake(509.000000,769.000000) controlPoint1:CGPointMake(479.958252,782.095642) controlPoint2:CGPointMake(496.425812,779.557007)]; - [complexShape addCurveToPoint:CGPointMake(570.000000,717.000000) controlPoint1:CGPointMake(536.317139,759.409058) controlPoint2:CGPointMake(549.962769,734.921936)]; - [complexShape addCurveToPoint:CGPointMake(632.000000,663.000000) controlPoint1:CGPointMake(586.772034,695.344360) controlPoint2:CGPointMake(605.504700,673.223633)]; - [complexShape addCurveToPoint:CGPointMake(679.000000,654.000000) controlPoint1:CGPointMake(645.724731,654.867432) controlPoint2:CGPointMake(663.048096,650.318481)]; - [complexShape addCurveToPoint:CGPointMake(696.000000,730.000000) controlPoint1:CGPointMake(702.263611,671.121704) controlPoint2:CGPointMake(698.817200,704.461426)]; - [complexShape addCurveToPoint:CGPointMake(685.000000,758.000000) controlPoint1:CGPointMake(693.381836,739.709473) controlPoint2:CGPointMake(689.682678,749.111511)]; - [complexShape addCurveToPoint:CGPointMake(639.000000,805.000000) controlPoint1:CGPointMake(674.516968,777.568542) controlPoint2:CGPointMake(664.349304,799.584106)]; - [complexShape addCurveToPoint:CGPointMake(605.000000,809.000000) controlPoint1:CGPointMake(627.873596,807.853577) controlPoint2:CGPointMake(616.339233,807.757568)]; - [complexShape addCurveToPoint:CGPointMake(559.000000,810.000000) controlPoint1:CGPointMake(589.680847,809.707275) controlPoint2:CGPointMake(574.264648,807.876892)]; - [complexShape addCurveToPoint:CGPointMake(491.000000,832.000000) controlPoint1:CGPointMake(535.253113,811.682922) controlPoint2:CGPointMake(510.867584,818.326538)]; - [complexShape addCurveToPoint:CGPointMake(483.000000,949.000000) controlPoint1:CGPointMake(452.488922,868.759766) controlPoint2:CGPointMake(506.715942,910.094971)]; - [complexShape addCurveToPoint:CGPointMake(438.000000,971.000000) controlPoint1:CGPointMake(471.816193,962.527100) controlPoint2:CGPointMake(455.094940,970.199341)]; - [complexShape addCurveToPoint:CGPointMake(381.000000,969.000000) controlPoint1:CGPointMake(419.703827,973.256775) controlPoint2:CGPointMake(398.264557,978.824951)]; - [complexShape addCurveToPoint:CGPointMake(332.000000,894.000000) controlPoint1:CGPointMake(336.834229,961.446167) controlPoint2:CGPointMake(348.029205,921.138672)]; - [complexShape addCurveToPoint:CGPointMake(201.000000,838.000000) controlPoint1:CGPointMake(307.704529,850.152588) controlPoint2:CGPointMake(249.883804,820.437744)]; - [complexShape addCurveToPoint:CGPointMake(167.000000,881.000000) controlPoint1:CGPointMake(182.977020,844.574768) controlPoint2:CGPointMake(166.990494,861.006348)]; - [complexShape addCurveToPoint:CGPointMake(175.000000,930.000000) controlPoint1:CGPointMake(165.412155,897.691711) controlPoint2:CGPointMake(168.718536,914.566101)]; - [complexShape addCurveToPoint:CGPointMake(169.000000,974.000000) controlPoint1:CGPointMake(175.880753,944.083801) controlPoint2:CGPointMake(184.246262,962.543579)]; - [complexShape addCurveToPoint:CGPointMake(112.000000,969.000000) controlPoint1:CGPointMake(153.879868,987.139404) controlPoint2:CGPointMake(121.948792,989.663391)]; - [complexShape addCurveToPoint:CGPointMake(99.000000,945.000000) controlPoint1:CGPointMake(105.050240,962.664062) controlPoint2:CGPointMake(100.558395,954.098572)]; - [complexShape addCurveToPoint:CGPointMake(97.000000,875.000000) controlPoint1:CGPointMake(95.831497,921.925537) controlPoint2:CGPointMake(94.644936,898.300354)]; - [complexShape addCurveToPoint:CGPointMake(132.000000,821.000000) controlPoint1:CGPointMake(107.640190,856.536194) controlPoint2:CGPointMake(116.238022,835.936340)]; - [complexShape addCurveToPoint:CGPointMake(184.000000,750.000000) controlPoint1:CGPointMake(155.798218,802.971130) controlPoint2:CGPointMake(173.768723,777.893677)]; - [complexShape addCurveToPoint:CGPointMake(187.000000,687.000000) controlPoint1:CGPointMake(185.273651,729.554688) controlPoint2:CGPointMake(197.039795,707.556824)]; - [complexShape addCurveToPoint:CGPointMake(154.000000,656.000000) controlPoint1:CGPointMake(180.328445,672.368408) controlPoint2:CGPointMake(164.994644,666.234436)]; - [complexShape addCurveToPoint:CGPointMake(196.000000,600.000000) controlPoint1:CGPointMake(149.363876,627.436218) controlPoint2:CGPointMake(180.133606,615.870422)]; - [complexShape addCurveToPoint:CGPointMake(268.000000,553.000000) controlPoint1:CGPointMake(219.965149,584.441223) controlPoint2:CGPointMake(243.722717,568.280640)]; - [complexShape addCurveToPoint:CGPointMake(295.000000,494.000000) controlPoint1:CGPointMake(285.621643,544.794250) controlPoint2:CGPointMake(308.387665,515.697510)]; - [complexShape addCurveToPoint:CGPointMake(223.000000,479.000000) controlPoint1:CGPointMake(274.950104,475.730682) controlPoint2:CGPointMake(247.686523,476.989319)]; - [complexShape addCurveToPoint:CGPointMake(115.000000,488.000000) controlPoint1:CGPointMake(190.196381,488.207886) controlPoint2:CGPointMake(149.008881,507.599640)]; - [complexShape addCurveToPoint:CGPointMake(92.000000,430.000000) controlPoint1:CGPointMake(91.723808,479.934326) controlPoint2:CGPointMake(81.161568,451.403015)]; - [complexShape addCurveToPoint:CGPointMake(185.000000,434.000000) controlPoint1:CGPointMake(119.158417,409.750031) controlPoint2:CGPointMake(157.642273,416.770874)]; - [complexShape addCurveToPoint:CGPointMake(298.000000,425.000000) controlPoint1:CGPointMake(219.467361,450.346191) controlPoint2:CGPointMake(268.697144,455.229706)]; - [complexShape addCurveToPoint:CGPointMake(339.000000,443.000000) controlPoint1:CGPointMake(312.625793,410.994751) controlPoint2:CGPointMake(331.307281,429.430817)]; - [complexShape addCurveToPoint:CGPointMake(349.000000,478.000000) controlPoint1:CGPointMake(344.453400,453.933380) controlPoint2:CGPointMake(347.832184,465.856812)]; - [complexShape addCurveToPoint:CGPointMake(344.000000,540.000000) controlPoint1:CGPointMake(351.007965,498.725006) controlPoint2:CGPointMake(352.137878,520.179993)]; - [complexShape addCurveToPoint:CGPointMake(316.000000,580.000000) controlPoint1:CGPointMake(339.491791,556.244446) controlPoint2:CGPointMake(328.167419,569.066284)]; - [complexShape addCurveToPoint:CGPointMake(273.000000,620.000000) controlPoint1:CGPointMake(299.305786,590.606079) controlPoint2:CGPointMake(286.016296,605.359131)]; - [complexShape addCurveToPoint:CGPointMake(219.000000,737.000000) controlPoint1:CGPointMake(253.199554,654.579529) controlPoint2:CGPointMake(210.386047,692.031433)]; - [complexShape addCurveToPoint:CGPointMake(249.000000,759.000000) controlPoint1:CGPointMake(225.442154,748.286621) controlPoint2:CGPointMake(236.628326,756.096069)]; - [complexShape addCurveToPoint:CGPointMake(334.000000,704.000000) controlPoint1:CGPointMake(285.551544,759.768677) controlPoint2:CGPointMake(315.180786,732.848572)]; - [complexShape addCurveToPoint:CGPointMake(352.000000,664.000000) controlPoint1:CGPointMake(342.386566,691.840271) controlPoint2:CGPointMake(348.465118,678.245239)]; - [complexShape addCurveToPoint:CGPointMake(363.000000,594.000000) controlPoint1:CGPointMake(356.415161,640.814575) controlPoint2:CGPointMake(362.213562,617.716248)]; - [complexShape addCurveToPoint:CGPointMake(375.000000,545.000000) controlPoint1:CGPointMake(364.278687,577.084534) controlPoint2:CGPointMake(369.613586,560.946472)]; - [complexShape addCurveToPoint:CGPointMake(370.000000,449.000000) controlPoint1:CGPointMake(385.057648,514.062866) controlPoint2:CGPointMake(387.877350,477.758911)]; - [complexShape addLineToPoint:CGPointMake(370.000000,449.000000)]; - [complexShape addLineToPoint:CGPointMake(349.000000,394.000000)]; - [complexShape addCurveToPoint:CGPointMake(317.000000,376.000000) controlPoint1:CGPointMake(342.462463,381.669800) controlPoint2:CGPointMake(329.632996,375.483673)]; - [complexShape addCurveToPoint:CGPointMake(288.000000,392.000000) controlPoint1:CGPointMake(302.854156,369.438843) controlPoint2:CGPointMake(296.932526,386.031342)]; - [complexShape closePath]; - [super setUp]; -} - -- (void)tearDown -{ - // Put teardown code here; it will be run once, after the last test case. - [super tearDown]; -} - --(CGFloat) round:(CGFloat)val to:(int)digits{ - double factor = pow(10, digits); - return roundf(val * factor) / factor; -} - --(BOOL) point:(CGPoint)p1 isNearTo:(CGPoint)p2{ - CGFloat xDiff = ABS(p2.x - p1.x); - CGFloat yDiff = ABS(p2.y - p1.y); - return xDiff < kIntersectionPointPrecision && yDiff < kIntersectionPointPrecision; -} - - --(BOOL) check:(CGFloat) f1 isLessThan:(CGFloat)f2 within:(CGFloat)marginOfError{ - if(f1 <= (f2 * (1.0f+marginOfError))){ - return YES; - } - NSLog(@"float value %f is > %f", f1, f2); - return NO; -} - --(BOOL) checkTanPoint:(CGFloat) f1 isLessThan:(CGFloat)f2{ - return [self check:f1 isLessThan:f2 within:.2]; -} - -@end diff --git a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m b/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m deleted file mode 100644 index 5cbd788e4..000000000 --- a/ios/PerformanceBezier/PerformanceBezierTests/PerformanceBezierClockwiseTests.m +++ /dev/null @@ -1,189 +0,0 @@ -// -// PerformanceBezierClockwiseTests.m -// PerformanceBezier -// -// Created by Adam Wulf on 1/7/14. -// Copyright (c) 2014 Milestone Made, LLC. All rights reserved. -// - -#import -#import "PerformanceBezierAbstractTest.h" - -@interface PerformanceBezierClockwiseTests : PerformanceBezierAbstractTest - -@end - -@implementation PerformanceBezierClockwiseTests - -- (void)setUp -{ - [super setUp]; - // Put setup code here; it will be run once, before the first test case. -} - -- (void)tearDown -{ - // Put teardown code here; it will be run once, after the last test case. - [super tearDown]; -} - -- (void)testLinearCounterClockwise -{ - UIBezierPath* simplePath = [UIBezierPath bezierPath]; - [simplePath moveToPoint:CGPointMake(100, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 99)]; - - XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testLinearClockwise -{ - UIBezierPath* simplePath = [UIBezierPath bezierPath]; - [simplePath moveToPoint:CGPointMake(100, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 101)]; - - XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testLinearEmptyShape -{ - UIBezierPath* simplePath = [UIBezierPath bezierPath]; - [simplePath moveToPoint:CGPointMake(100, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 101)]; - [simplePath closePath]; - - XCTAssertTrue([simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testLinearEmptyShape2 -{ - UIBezierPath* simplePath = [UIBezierPath bezierPath]; - [simplePath moveToPoint:CGPointMake(100, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 100)]; - [simplePath addLineToPoint:CGPointMake(200, 99)]; - [simplePath closePath]; - - XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testSimpleClockwiseCurve -{ - UIBezierPath* simplePath = [UIBezierPath bezierPath]; - [simplePath moveToPoint:CGPointMake(100, 100)]; - [simplePath addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(100, 0) controlPoint2:CGPointMake(200, 0)]; - - XCTAssertTrue([simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testSimpleCounterClockwiseCurve -{ - UIBezierPath* simplePath = [UIBezierPath bezierPath]; - [simplePath moveToPoint:CGPointMake(100, 100)]; - [simplePath addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(100, 200) controlPoint2:CGPointMake(200, 200)]; - - XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testSimplePath -{ - UIBezierPath* simplePath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:10 startAngle:0 endAngle:M_PI clockwise:YES]; - - XCTAssertTrue([simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testSimplePath2 -{ - UIBezierPath* simplePath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:10 startAngle:0 endAngle:M_PI clockwise:NO]; - - XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct"); -} - -- (void)testComplexPath -{ - XCTAssertTrue([self.complexShape isClockwise], @"clockwise is correct"); -} - -- (void)testReversedComplexPath -{ - XCTAssertTrue(![[self.complexShape bezierPathByReversingPath] isClockwise], @"clockwise is correct"); -} - -- (void)testFirstAndLastPointRect { - // This is an example of a functional test case. - - UIBezierPath* path1 = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 20, 20)]; - XCTAssertEqual([path1 elementCount], 5, "element count is correct"); - XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].y, (CGFloat) 10, "element count is correct"); -} - -- (void)testFirstAndLastPointLine { - // This is an example of a functional test case. - - UIBezierPath* path1 = [UIBezierPath bezierPath]; - [path1 moveToPoint:CGPointMake(10, 10)]; - [path1 addLineToPoint:CGPointMake(30, 30)]; - XCTAssertEqual([path1 elementCount], 2, "element count is correct"); - XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct"); - XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct"); -} - - -- (void)testFirstAndLastPointCurve { - // This is an example of a functional test case. - - UIBezierPath* path1 = [UIBezierPath bezierPath]; - [path1 moveToPoint:CGPointMake(10, 10)]; - [path1 addCurveToPoint:CGPointMake(30, 30) controlPoint1:CGPointMake(15, 15) controlPoint2:CGPointMake(25, 25)]; - XCTAssertEqual([path1 elementCount], 2, "element count is correct"); - XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct"); - XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct"); -} - -- (void)testFirstAndLastPointQuadCurve { - // This is an example of a functional test case. - - UIBezierPath* path1 = [UIBezierPath bezierPath]; - [path1 moveToPoint:CGPointMake(10, 10)]; - [path1 addQuadCurveToPoint:CGPointMake(30, 30) controlPoint:CGPointMake(20, 20)]; - XCTAssertEqual([path1 elementCount], 2, "element count is correct"); - XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct"); - XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct"); -} - -- (void)testFirstAndLastPointMoveTo { - // This is an example of a functional test case. - - UIBezierPath* path1 = [UIBezierPath bezierPath]; - [path1 moveToPoint:CGPointMake(10, 10)]; - XCTAssertEqual([path1 elementCount], 1, "element count is correct"); - XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].y, (CGFloat) 10, "element count is correct"); -} - -- (void)testFirstAndLastPointMoveTo2 { - // This is an example of a functional test case. - - UIBezierPath* path1 = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 20, 20)]; - [path1 moveToPoint:CGPointMake(50, 50)]; - XCTAssertEqual([path1 elementCount], 6, "element count is correct"); - XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct"); - XCTAssertEqual([path1 lastPoint].x, (CGFloat) 50, "element count is correct"); - XCTAssertEqual([path1 lastPoint].y, (CGFloat) 50, "element count is correct"); -} - -@end diff --git a/ios/PerformanceBezier/README.md b/ios/PerformanceBezier/README.md deleted file mode 100644 index efebe748a..000000000 --- a/ios/PerformanceBezier/README.md +++ /dev/null @@ -1,67 +0,0 @@ -iOS UIBezierPath Performance -===== - -This code dramatically improves performance for common UIBezierPath operations, and it also -brings UIBezierPath API closer to its NSBezierPath counterpart. For full background of this -repo, checkout [the blogpost explaining what this framework does](http://blog.getlooseleaf.com/post/110511009139/improving-uibezierpath-performance-and-api). - -This code was originally part of [Loose Leaf](https://getlooseleaf.com/). Additional components and -libraries from the app [have also been open sourced](https://getlooseleaf.com/opensource/). - -## What is this? - -This framework adds caching into every UIBezierPath so that common operations can -be performed in constant time. It also adds some missing NSBezierPath methods to the -UIBezierPath class. - -After linking this framework into your project, all Bezier paths will automatically be upgraded -to use this new caching. No custom UIBezierPath allocation or initialization is required. - -For example, by default there is no O(1) way to retrieve elements from a UIBezierPath. In order to -retrieve the first point of the curve, you must CGPathApply() and interate over the entire path -to retrieve that single point. This framework changes that. For many algorithms, this can -dramatically affect performance. - -## Are you using PerformanceBezier? - -Let me know! I'd love to know where PerformanceBezier is using and how it's affecting your apps. Ping me -at [@adamwulf](https://twitter.com/adamwulf)! - -Also - If you like PerformanceBezier, then you'll _love_ [ClippingBezier](https://github.com/adamwulf/ClippingBezier) - an easy way to find intersecting points, lines, and shapes between two UIBezierPaths. - -## Documentation - -View the header files for full documentation. - -## Building the framework - -This library will generate a proper static framework bundle that can be used in any iOS7+ project. - -## Including in your project - -1. Link against the built framework. -2. Add "-ObjC++ -lstdc++" to the Other Linker Flags in the project's Settings -3. #import <PerformanceBezier/PerformanceBezier.h> - -## JRSwizzle - -This framework includes and uses the [JRSwizzle](https://github.com/rentzsch/jrswizzle) library, which is -licensed under the MIT license. - -## License - -Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 United States License. - -For attribution, please include: - -1. Mention original author "Adam Wulf for Loose Leaf app" -2. Link to https://getlooseleaf.com/opensource/ -3. Link to https://github.com/adamwulf/PerformanceBezier - - - -## Support this framework - -This framework is created by Adam Wulf ([@adamwulf](https://twitter.com/adamwulf)) as a part of the [Loose Leaf app](https://getlooseleaf.com). - -[Buy the app](https://itunes.apple.com/us/app/loose-leaf/id625659452?mt=8&uo=4&at=10lNUI&ct=github) to show your support! :) diff --git a/ios/PerformanceBezier/SubdivideLicense b/ios/PerformanceBezier/SubdivideLicense deleted file mode 100644 index 119a1a69c..000000000 --- a/ios/PerformanceBezier/SubdivideLicense +++ /dev/null @@ -1,32 +0,0 @@ -/* -* Bezier path utility category (trimming) -* -* (c) 2004 Alastair J. Houghton -* All Rights Reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in the -* documentation and/or other materials provided with the distribution. -* -* 3. The name of the author of this software may not be used to endorse -* or promote products derived from the software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -* IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, -* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO -* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -*/ \ No newline at end of file diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 7cbac8ab1..ddb63881c 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -825,7 +825,7 @@ - (void)setupTextPath:(CGContextRef)context textPathPath = [textPath getPath]; _path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; _path = [_path bezierPathByFlatteningPathAndImmutable:YES]; - pathLength = [_path length]; + pathLength = [_path pathLength]; isClosed = [_path isClosed]; return NO; } diff --git a/package.json b/package.json index c50cfcb75..5a25a8c75 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.4.1", + "version": "6.0.0-rc1", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 7e1cb4de3f4d3ed48b80606214c34a816fb2304d Mon Sep 17 00:00:00 2001 From: Horcrux Date: Tue, 7 Nov 2017 17:26:47 +0800 Subject: [PATCH 190/198] fix conflicts --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 4e2761475..760c9d1d6 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,5 @@ { -<<<<<<< HEAD "version": "6.0.0-rc1", -======= - "version": "5.5.0", ->>>>>>> master "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 2998aa1bb6689d5d165833fc7371c18ad096ffac Mon Sep 17 00:00:00 2001 From: Horcrux Date: Tue, 7 Nov 2017 18:49:53 +0800 Subject: [PATCH 191/198] Download Quartz and PerformanceBezier from github --- .gitmodules | 4 - ios/PerformanceBezier | 1 - ios/QuartzBookPack/Bezier/Bezier.h | 17 - ios/QuartzBookPack/Bezier/BezierElement.h | 34 - ios/QuartzBookPack/Bezier/BezierElement.m | 163 ---- ios/QuartzBookPack/Bezier/BezierFunctions.h | 38 - ios/QuartzBookPack/Bezier/BezierFunctions.m | 205 ----- ios/QuartzBookPack/Bezier/BezierUtils.h | 87 --- ios/QuartzBookPack/Bezier/BezierUtils.m | 709 ------------------ .../Bezier/UIBezierPath+Elements.h | 56 -- .../Bezier/UIBezierPath+Elements.m | 519 ------------- ios/QuartzBookPack/Drawing/Drawing-Block.h | 22 - ios/QuartzBookPack/Drawing/Drawing-Block.m | 71 -- ios/QuartzBookPack/Drawing/Drawing-Gradient.h | 44 -- ios/QuartzBookPack/Drawing/Drawing-Gradient.m | 256 ------- ios/QuartzBookPack/Drawing/Drawing-Util.h | 25 - ios/QuartzBookPack/Drawing/Drawing-Util.m | 241 ------ ios/QuartzBookPack/Geometry/BaseGeometry.h | 70 -- ios/QuartzBookPack/Geometry/BaseGeometry.m | 242 ------ ios/QuartzBookPack/Image/ImageUtils.h | 40 - ios/QuartzBookPack/Image/ImageUtils.m | 372 --------- ios/QuartzBookPack/TextDrawing/Drawing-Text.h | 23 - ios/QuartzBookPack/TextDrawing/Drawing-Text.m | 175 ----- .../TextDrawing/UIBezierPath+Text.h | 13 - .../TextDrawing/UIBezierPath+Text.m | 56 -- ios/QuartzBookPack/Utility/Utility.h | 48 -- ios/QuartzBookPack/Utility/Utility.m | 240 ------ ios/RNSVG.xcodeproj/project.pbxproj | 48 -- package.json | 8 +- scripts/install.js | 20 + 30 files changed, 25 insertions(+), 3822 deletions(-) delete mode 160000 ios/PerformanceBezier delete mode 100644 ios/QuartzBookPack/Bezier/Bezier.h delete mode 100644 ios/QuartzBookPack/Bezier/BezierElement.h delete mode 100644 ios/QuartzBookPack/Bezier/BezierElement.m delete mode 100644 ios/QuartzBookPack/Bezier/BezierFunctions.h delete mode 100644 ios/QuartzBookPack/Bezier/BezierFunctions.m delete mode 100644 ios/QuartzBookPack/Bezier/BezierUtils.h delete mode 100644 ios/QuartzBookPack/Bezier/BezierUtils.m delete mode 100644 ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h delete mode 100644 ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m delete mode 100644 ios/QuartzBookPack/Drawing/Drawing-Block.h delete mode 100644 ios/QuartzBookPack/Drawing/Drawing-Block.m delete mode 100644 ios/QuartzBookPack/Drawing/Drawing-Gradient.h delete mode 100644 ios/QuartzBookPack/Drawing/Drawing-Gradient.m delete mode 100644 ios/QuartzBookPack/Drawing/Drawing-Util.h delete mode 100644 ios/QuartzBookPack/Drawing/Drawing-Util.m delete mode 100644 ios/QuartzBookPack/Geometry/BaseGeometry.h delete mode 100644 ios/QuartzBookPack/Geometry/BaseGeometry.m delete mode 100644 ios/QuartzBookPack/Image/ImageUtils.h delete mode 100644 ios/QuartzBookPack/Image/ImageUtils.m delete mode 100644 ios/QuartzBookPack/TextDrawing/Drawing-Text.h delete mode 100644 ios/QuartzBookPack/TextDrawing/Drawing-Text.m delete mode 100644 ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h delete mode 100644 ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m delete mode 100644 ios/QuartzBookPack/Utility/Utility.h delete mode 100644 ios/QuartzBookPack/Utility/Utility.m create mode 100644 scripts/install.js diff --git a/.gitmodules b/.gitmodules index 120d2ae80..3c3d57e09 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ [submodule "Example"] path = Example url = https://github.com/magicismight/react-native-svg-example.git - -[submodule "ios/PerformanceBezier"] - path = ios/PerformanceBezier - url = https://github.com/adamwulf/PerformanceBezier.git diff --git a/ios/PerformanceBezier b/ios/PerformanceBezier deleted file mode 160000 index 0e0ad4b03..000000000 --- a/ios/PerformanceBezier +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e0ad4b0303ec80c3c32257addfef9ef6e2a2eb7 diff --git a/ios/QuartzBookPack/Bezier/Bezier.h b/ios/QuartzBookPack/Bezier/Bezier.h deleted file mode 100644 index a418bf3a6..000000000 --- a/ios/QuartzBookPack/Bezier/Bezier.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - BEZIER PACK - - */ - -#import -#import -#import - -#import "BaseGeometry.h" -#import "BezierUtils.h" -#import "BezierElement.h" -#import "BezierFunctions.h" -#import "UIBezierPath+Elements.h" diff --git a/ios/QuartzBookPack/Bezier/BezierElement.h b/ios/QuartzBookPack/Bezier/BezierElement.h deleted file mode 100644 index 315a156cf..000000000 --- a/ios/QuartzBookPack/Bezier/BezierElement.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - - -#import -#import - -// Block for transforming points -typedef CGPoint (^PathBlock)(CGPoint point); - -@interface BezierElement : NSObject - -// Element storage -@property (nonatomic, assign) CGPathElementType elementType; -@property (nonatomic, assign) CGPoint point; -@property (nonatomic, assign) CGPoint controlPoint1; -@property (nonatomic, assign) CGPoint controlPoint2; - -// Instance creation -+ (instancetype) elementWithPathElement: (CGPathElement) element; - -// Applying transformations -- (BezierElement *) elementByApplyingBlock: (PathBlock) block; - -// Adding to path -- (void) addToPath: (UIBezierPath *) path; - -// Readable forms -@property (nonatomic, readonly) NSString *stringValue; -- (void) showTheCode; -@end; diff --git a/ios/QuartzBookPack/Bezier/BezierElement.m b/ios/QuartzBookPack/Bezier/BezierElement.m deleted file mode 100644 index 057a2bea7..000000000 --- a/ios/QuartzBookPack/Bezier/BezierElement.m +++ /dev/null @@ -1,163 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - - -#import "Bezier.h" - -#pragma mark - Bezier Element - - -@implementation BezierElement -- (instancetype) init -{ - self = [super init]; - if (self) - { - _elementType = kCGPathElementMoveToPoint; - _point = NULLPOINT; - _controlPoint1 = NULLPOINT; - _controlPoint2 = NULLPOINT; - } - return self; -} - -+ (instancetype) elementWithPathElement: (CGPathElement) element -{ - BezierElement *newElement = [[self alloc] init]; - newElement.elementType = element.type; - - switch (newElement.elementType) - { - case kCGPathElementCloseSubpath: - break; - case kCGPathElementMoveToPoint: - case kCGPathElementAddLineToPoint: - { - newElement.point = element.points[0]; - break; - } - case kCGPathElementAddQuadCurveToPoint: - { - newElement.point = element.points[1]; - newElement.controlPoint1 = element.points[0]; - break; - } - case kCGPathElementAddCurveToPoint: - { - newElement.point = element.points[2]; - newElement.controlPoint1 = element.points[0]; - newElement.controlPoint2 = element.points[1]; - break; - } - default: - break; - } - - return newElement; -} - -- (instancetype) copyWithZone: (NSZone *) zone -{ - BezierElement *theCopy = [[[self class] allocWithZone:zone] init]; - if (theCopy) - { - theCopy.elementType = _elementType; - theCopy.point = _point; - theCopy.controlPoint1 = _controlPoint1; - theCopy.controlPoint2 = _controlPoint2; - } - return theCopy; -} - -#pragma mark - Path - -- (BezierElement *) elementByApplyingBlock: (PathBlock) block -{ - BezierElement *output = [self copy]; - if (!block) - return output; - - if (!POINT_IS_NULL(output.point)) - output.point = block(output.point); - if (!POINT_IS_NULL(output.controlPoint1)) - output.controlPoint1 = block(output.controlPoint1); - if (!POINT_IS_NULL(output.controlPoint2)) - output.controlPoint2 = block(output.controlPoint2); - return output; -} - -- (void) addToPath: (UIBezierPath *) path -{ - switch (self.elementType) - { - case kCGPathElementCloseSubpath: - [path closePath]; - break; - case kCGPathElementMoveToPoint: - [path moveToPoint:self.point]; - break; - case kCGPathElementAddLineToPoint: - [path addLineToPoint:self.point]; - break; - case kCGPathElementAddQuadCurveToPoint: - [path addQuadCurveToPoint:self.point controlPoint:self.controlPoint1]; - break; - case kCGPathElementAddCurveToPoint: - [path addCurveToPoint:self.point controlPoint1:self.controlPoint1 controlPoint2:self.controlPoint2]; - break; - default: - break; - } -} - -#pragma mark - Strings - -- (NSString *) stringValue -{ - switch (self.elementType) - { - case kCGPathElementCloseSubpath: - return @"Close Path"; - case kCGPathElementMoveToPoint: - return [NSString stringWithFormat:@"Move to point %@", POINTSTRING(self.point)]; - case kCGPathElementAddLineToPoint: - return [NSString stringWithFormat:@"Add line to point %@", POINTSTRING(self.point)]; - case kCGPathElementAddQuadCurveToPoint: - return [NSString stringWithFormat:@"Add quad curve to point %@ with control point %@", POINTSTRING(self.point), POINTSTRING(self.controlPoint1)]; - case kCGPathElementAddCurveToPoint: - return [NSString stringWithFormat:@"Add curve to point %@ with control points %@ and %@", POINTSTRING(self.point), POINTSTRING(self.controlPoint1), POINTSTRING(self.controlPoint2)]; - } - return nil; -} - -- (void) showTheCode -{ - switch (self.elementType) - { - case kCGPathElementCloseSubpath: - printf(" [path closePath];\n\n"); - break; - case kCGPathElementMoveToPoint: - printf(" [path moveToPoint:CGPointMake(%f, %f)];\n", - self.point.x, self.point.y); - break; - case kCGPathElementAddLineToPoint: - printf(" [path addLineToPoint:CGPointMake(%f, %f)];\n", - self.point.x, self.point.y); - break; - case kCGPathElementAddQuadCurveToPoint: - printf(" [path addQuadCurveToPoint:CGPointMake(%f, %f) controlPoint:CGPointMake(%f, %f)];\n", - self.point.x, self.point.y, self.controlPoint1.x, self.controlPoint1.y); - break; - case kCGPathElementAddCurveToPoint: - printf(" [path addCurveToPoint:CGPointMake(%f, %f) controlPoint1:CGPointMake(%f, %f) controlPoint2:CGPointMake(%f, %f)];\n", - self.point.x, self.point.y, self.controlPoint1.x, self.controlPoint1.y, self.controlPoint2.x, self.controlPoint2.y); - break; - default: - break; - } -} -@end - diff --git a/ios/QuartzBookPack/Bezier/BezierFunctions.h b/ios/QuartzBookPack/Bezier/BezierFunctions.h deleted file mode 100644 index 74053634f..000000000 --- a/ios/QuartzBookPack/Bezier/BezierFunctions.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import "BezierElement.h" - -#define NUMBER_OF_BEZIER_SAMPLES 6 - -typedef CGFloat (^InterpolationBlock)(CGFloat percent); - -// Return Bezier Value -float CubicBezier(float t, float start, float c1, float c2, float end); -float QuadBezier(float t, float start, float c1, float end); - -// Return Bezier Point -CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end); -CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end); - -// Calculate Curve Length -float CubicBezierLength(CGPoint start, CGPoint c1, CGPoint c2, CGPoint end); -float QuadBezierLength(CGPoint start, CGPoint c1, CGPoint end); - -// Element Distance -CGFloat ElementDistanceFromPoint(BezierElement *element, CGPoint point, CGPoint startPoint); - -// Linear Interpolation -CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope); - -// Interpolate along element -CGPoint InterpolatePointFromElement(BezierElement *element, CGPoint point, CGPoint startPoint, CGFloat percent, CGPoint *slope); - -// Ease -CGFloat EaseIn(CGFloat currentTime, int factor); -CGFloat EaseOut(CGFloat currentTime, int factor); -CGFloat EaseInOut(CGFloat currentTime, int factor); diff --git a/ios/QuartzBookPack/Bezier/BezierFunctions.m b/ios/QuartzBookPack/Bezier/BezierFunctions.m deleted file mode 100644 index 6003e404f..000000000 --- a/ios/QuartzBookPack/Bezier/BezierFunctions.m +++ /dev/null @@ -1,205 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "Bezier.h" - -#pragma mark - Bezier functions -float CubicBezier(float t, float start, float c1, float c2, float end) -{ - CGFloat t_ = (1.0 - t); - CGFloat tt_ = t_ * t_; - CGFloat ttt_ = t_ * t_ * t_; - CGFloat tt = t * t; - CGFloat ttt = t * t * t; - - return start * ttt_ - + 3.0 * c1 * tt_ * t - + 3.0 * c2 * t_ * tt - + end * ttt; -} - -float QuadBezier(float t, float start, float c1, float end) -{ - CGFloat t_ = (1.0 - t); - CGFloat tt_ = t_ * t_; - CGFloat tt = t * t; - - return start * tt_ - + 2.0 * c1 * t_ * t - + end * tt; -} - -CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end) -{ - CGPoint result; - result.x = CubicBezier(t, start.x, c1.x, c2.x, end.x); - result.y = CubicBezier(t, start.y, c1.y, c2.y, end.y); - return result; -} - -CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end) -{ - CGPoint result; - result.x = QuadBezier(t, start.x, c1.x, end.x); - result.y = QuadBezier(t, start.y, c1.y, end.y); - return result; -} - -float CubicBezierLength(CGPoint start, CGPoint c1, CGPoint c2, CGPoint end) -{ - int steps = NUMBER_OF_BEZIER_SAMPLES; - CGPoint current; - CGPoint previous; - float length = 0.0; - - for (int i = 0; i <= steps; i++) - { - float t = (float) i / (float) steps; - current = CubicBezierPoint(t, start, c1, c2, end); - if (i > 0) - length += PointDistanceFromPoint(current, previous); - previous = current; - } - - return length; -} - -float QuadBezierLength(CGPoint start, CGPoint c1, CGPoint end) -{ - int steps = NUMBER_OF_BEZIER_SAMPLES; - CGPoint current; - CGPoint previous; - float length = 0.0; - - for (int i = 0; i <= steps; i++) - { - float t = (float) i / (float) steps; - current = QuadBezierPoint(t, start, c1, end); - if (i > 0) - length += PointDistanceFromPoint(current, previous); - previous = current; - } - - return length; -} - - -#pragma mark - Point Percents -#define USE_CURVE_TWEAK 0 -#if USE_CURVE_TWEAK -#define CURVETWEAK 0.8 -#else -#define CURVETWEAK 1.0 -#endif - -CGFloat ElementDistanceFromPoint(BezierElement *element, CGPoint point, CGPoint startPoint) -{ - CGFloat distance = 0.0f; - switch (element.elementType) - { - case kCGPathElementMoveToPoint: - return 0.0f; - case kCGPathElementCloseSubpath: - return PointDistanceFromPoint(point, startPoint); - case kCGPathElementAddLineToPoint: - return PointDistanceFromPoint(point, element.point); - case kCGPathElementAddCurveToPoint: - return CubicBezierLength(point, element.controlPoint1, element.controlPoint2, element.point) * CURVETWEAK; - case kCGPathElementAddQuadCurveToPoint: - return QuadBezierLength(point, element.controlPoint1, element.point) * CURVETWEAK; - } - - return distance; -} - -// Centralize for both close subpath and add line to point -CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope) -{ - CGFloat dx = p2.x - p1.x; - CGFloat dy = p2.y - p1.y; - - if (slope) - *slope = CGPointMake(dx, dy); - - CGFloat px = p1.x + dx * percent; - CGFloat py = p1.y + dy * percent; - - return CGPointMake(px, py); -} - -// Interpolate along element -CGPoint InterpolatePointFromElement(BezierElement *element, CGPoint point, CGPoint startPoint, CGFloat percent, CGPoint *slope) -{ - switch (element.elementType) - { - case kCGPathElementMoveToPoint: - { - // No distance - if (slope) - *slope = CGPointMake(INFINITY, INFINITY); - return point; - } - - case kCGPathElementCloseSubpath: - { - // from self.point to firstPoint - CGPoint p = InterpolateLineSegment(point, startPoint, percent, slope); - return p; - } - - case kCGPathElementAddLineToPoint: - { - // from point to self.point - CGPoint p = InterpolateLineSegment(point, element.point, percent, slope); - return p; - } - - case kCGPathElementAddQuadCurveToPoint: - { - // from point to self.point - CGPoint p = QuadBezierPoint(percent, point, element.controlPoint1, element.point); - CGFloat dx = p.x - QuadBezier(percent * 0.9, point.x, element.controlPoint1.x, element.point.x); - CGFloat dy = p.y - QuadBezier(percent * 0.9, point.y, element.controlPoint1.y, element.point.y); - if (slope) - *slope = CGPointMake(dx, dy); - return p; - } - - case kCGPathElementAddCurveToPoint: - { - // from point to self.point - CGPoint p = CubicBezierPoint(percent, point, element.controlPoint1, element.controlPoint2, element.point); - CGFloat dx = p.x - CubicBezier(percent * 0.9, point.x, element.controlPoint1.x, element.controlPoint2.x, element.point.x); - CGFloat dy = p.y - CubicBezier(percent * 0.9, point.y, element.controlPoint1.y, element.controlPoint2.y, element.point.y); - if (slope) - *slope = CGPointMake(dx, dy); - return p; - } - } - - return NULLPOINT; -} - -CGFloat EaseIn(CGFloat currentTime, int factor) -{ - return powf(currentTime, factor); -} - -CGFloat EaseOut(CGFloat currentTime, int factor) -{ - return 1 - powf((1 - currentTime), factor); -} - -CGFloat EaseInOut(CGFloat currentTime, int factor) -{ - currentTime = currentTime * 2.0; - if (currentTime < 1) - return (0.5 * pow(currentTime, factor)); - currentTime -= 2.0; - if (factor % 2) - return 0.5 * (pow(currentTime, factor) + 2.0); - return 0.5 * (2.0 - pow(currentTime, factor)); -} \ No newline at end of file diff --git a/ios/QuartzBookPack/Bezier/BezierUtils.h b/ios/QuartzBookPack/Bezier/BezierUtils.h deleted file mode 100644 index 84c1794a6..000000000 --- a/ios/QuartzBookPack/Bezier/BezierUtils.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import "BaseGeometry.h" - -CGRect PathBoundingBox(UIBezierPath *path); -CGRect PathBoundingBoxWithLineWidth(UIBezierPath *path); -CGPoint PathBoundingCenter(UIBezierPath *path); -CGPoint PathCenter(UIBezierPath *path); - -// Transformations -void ApplyCenteredPathTransform(UIBezierPath *path, CGAffineTransform transform); -UIBezierPath *PathByApplyingTransform(UIBezierPath *path, CGAffineTransform transform); - -// Utility -void RotatePath(UIBezierPath *path, CGFloat theta); -void ScalePath(UIBezierPath *path, CGFloat sx, CGFloat sy); -void OffsetPath(UIBezierPath *path, CGSize offset); -void MovePathToPoint(UIBezierPath *path, CGPoint point); -void MovePathCenterToPoint(UIBezierPath *path, CGPoint point); -void MirrorPathHorizontally(UIBezierPath *path); -void MirrorPathVertically(UIBezierPath *path); - -// Fitting -void FitPathToRect(UIBezierPath *path, CGRect rect); -void AdjustPathToRect(UIBezierPath *path, CGRect destRect); - -// Path Attributes -void CopyBezierState(UIBezierPath *source, UIBezierPath *destination); -void CopyBezierDashes(UIBezierPath *source, UIBezierPath *destination); -void AddDashesToPath(UIBezierPath *path); - -// String to Path -UIBezierPath *BezierPathFromString(NSString *string, UIFont *font); -UIBezierPath *BezierPathFromStringWithFontFace(NSString *string, NSString *fontFace); - -// Draw Text in Path -void DrawAttributedStringInBezierPath(UIBezierPath *path, NSAttributedString *attributedString); -void DrawAttributedStringInBezierSubpaths(UIBezierPath *path, NSAttributedString *attributedString); - -// N-Gons -UIBezierPath *BezierPolygon(NSUInteger numberOfSides); -UIBezierPath *BezierInflectedShape(NSUInteger numberOfInflections, CGFloat percentInflection); -UIBezierPath *BezierStarShape(NSUInteger numberOfInflections, CGFloat percentInflection); - -// Misc -void ClipToRect(CGRect rect); -void FillRect(CGRect rect, UIColor *color); -void ShowPathProgression(UIBezierPath *path, CGFloat maxPercent); - -// Effects -void SetShadow(UIColor *color, CGSize size, CGFloat blur); -void DrawShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur); -void DrawInnerShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur); -void EmbossPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat blur); -void BevelPath(UIBezierPath *p, UIColor *color, CGFloat r, CGFloat theta); -void InnerBevel(UIBezierPath *p, UIColor *color, CGFloat r, CGFloat theta); -void ExtrudePath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat angle); - -@interface UIBezierPath (HandyUtilities) -@property (nonatomic, readonly) CGPoint center; -@property (nonatomic, readonly) CGRect computedBounds; -@property (nonatomic, readonly) CGRect computedBoundsWithLineWidth; - -// Stroke/Fill -- (void) stroke: (CGFloat) width; -- (void) stroke: (CGFloat) width color: (UIColor *) color; -- (void) strokeInside: (CGFloat) width; -- (void) strokeInside: (CGFloat) width color: (UIColor *) color; -- (void) fill: (UIColor *) fillColor; -- (void) fill: (UIColor *) fillColor withMode: (CGBlendMode) blendMode; -- (void) fillWithNoise: (UIColor *) fillColor; -- (void) addDashes; -- (void) addDashes: (NSArray *) pattern; -- (void) applyPathPropertiesToContext; - -// Clipping -- (void) clipToPath; // I hate addClip -- (void) clipToStroke: (NSUInteger) width; - -// Util -- (UIBezierPath *) safeCopy; -@end diff --git a/ios/QuartzBookPack/Bezier/BezierUtils.m b/ios/QuartzBookPack/Bezier/BezierUtils.m deleted file mode 100644 index 018a1c129..000000000 --- a/ios/QuartzBookPack/Bezier/BezierUtils.m +++ /dev/null @@ -1,709 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import "BezierUtils.h" -#import "Utility.h" - -#pragma mark - Bounds -CGRect PathBoundingBox(UIBezierPath *path) -{ - return CGPathGetPathBoundingBox(path.CGPath); -} - -CGRect PathBoundingBoxWithLineWidth(UIBezierPath *path) -{ - CGRect bounds = PathBoundingBox(path); - return CGRectInset(bounds, -path.lineWidth / 2.0f, -path.lineWidth / 2.0f); -} - -CGPoint PathBoundingCenter(UIBezierPath *path) -{ - return RectGetCenter(PathBoundingBox(path)); -} - -CGPoint PathCenter(UIBezierPath *path) -{ - return RectGetCenter(path.bounds); -} - -#pragma mark - Misc -void ClipToRect(CGRect rect) -{ - [[UIBezierPath bezierPathWithRect:rect] addClip]; -} - -void FillRect(CGRect rect, UIColor *color) -{ - [[UIBezierPath bezierPathWithRect:rect] fill:color]; -} - -void ShowPathProgression(UIBezierPath *path, CGFloat maxPercent) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGFloat maximumPercent = fmax(fmin(maxPercent, 1.0f), 0.0f); - PushDraw(^{ - CGFloat distance = path.pathLength; - int samples = distance / 6; - float dLevel = 0.75 / (CGFloat) samples; - - UIBezierPath *marker; - for (int i = 0; i <= samples * maximumPercent; i++) - { - CGFloat percent = (CGFloat) i / (CGFloat) samples; - CGPoint point = [path pointAtPercent:percent withSlope:NULL]; - UIColor *color = [UIColor colorWithWhite:i * dLevel alpha:1]; - - CGRect r = RectAroundCenter(point, CGSizeMake(2, 2)); - marker = [UIBezierPath bezierPathWithOvalInRect:r]; - [marker fill:color]; - } - }); -} - -#pragma mark - Transform -void ApplyCenteredPathTransform(UIBezierPath *path, CGAffineTransform transform) -{ - CGPoint center = PathBoundingCenter(path); - CGAffineTransform t = CGAffineTransformIdentity; - t = CGAffineTransformTranslate(t, center.x, center.y); - t = CGAffineTransformConcat(transform, t); - t = CGAffineTransformTranslate(t, -center.x, -center.y); - [path applyTransform:t]; -} - -UIBezierPath *PathByApplyingTransform(UIBezierPath *path, CGAffineTransform transform) -{ - UIBezierPath *copy = [path copy]; - ApplyCenteredPathTransform(copy, transform); - return copy; -} - -void RotatePath(UIBezierPath *path, CGFloat theta) -{ - CGAffineTransform t = CGAffineTransformMakeRotation(theta); - ApplyCenteredPathTransform(path, t); -} - -void ScalePath(UIBezierPath *path, CGFloat sx, CGFloat sy) -{ - CGAffineTransform t = CGAffineTransformMakeScale(sx, sy); - ApplyCenteredPathTransform(path, t); -} - -void OffsetPath(UIBezierPath *path, CGSize offset) -{ - CGAffineTransform t = CGAffineTransformMakeTranslation(offset.width, offset.height); - ApplyCenteredPathTransform(path, t); -} - -void MovePathToPoint(UIBezierPath *path, CGPoint destPoint) -{ - CGRect bounds = PathBoundingBox(path); - CGPoint p1 = bounds.origin; - CGPoint p2 = destPoint; - CGSize vector = CGSizeMake(p2.x - p1.x, p2.y - p1.y); - OffsetPath(path, vector); -} - -void MovePathCenterToPoint(UIBezierPath *path, CGPoint destPoint) -{ - CGRect bounds = PathBoundingBox(path); - CGPoint p1 = bounds.origin; - CGPoint p2 = destPoint; - CGSize vector = CGSizeMake(p2.x - p1.x, p2.y - p1.y); - vector.width -= bounds.size.width / 2.0f; - vector.height -= bounds.size.height / 2.0f; - OffsetPath(path, vector); -} - -void MirrorPathHorizontally(UIBezierPath *path) -{ - CGAffineTransform t = CGAffineTransformMakeScale(-1, 1); - ApplyCenteredPathTransform(path, t); -} - -void MirrorPathVertically(UIBezierPath *path) -{ - CGAffineTransform t = CGAffineTransformMakeScale(1, -1); - ApplyCenteredPathTransform(path, t); -} - -void FitPathToRect(UIBezierPath *path, CGRect destRect) -{ - CGRect bounds = PathBoundingBox(path); - CGRect fitRect = RectByFittingRect(bounds, destRect); - CGFloat scale = AspectScaleFit(bounds.size, destRect); - - CGPoint newCenter = RectGetCenter(fitRect); - MovePathCenterToPoint(path, newCenter); - ScalePath(path, scale, scale); -} - -void AdjustPathToRect(UIBezierPath *path, CGRect destRect) -{ - CGRect bounds = PathBoundingBox(path); - CGFloat scaleX = destRect.size.width / bounds.size.width; - CGFloat scaleY = destRect.size.height / bounds.size.height; - - CGPoint newCenter = RectGetCenter(destRect); - MovePathCenterToPoint(path, newCenter); - ScalePath(path, scaleX, scaleY); -} - -#pragma mark - Path Attributes -void AddDashesToPath(UIBezierPath *path) -{ - CGFloat dashes[] = {6, 2}; - [path setLineDash:dashes count:2 phase:0]; -} - -void CopyBezierDashes(UIBezierPath *source, UIBezierPath *destination) -{ - NSInteger count; - [source getLineDash:NULL count:&count phase:NULL]; - - CGFloat phase; - CGFloat *pattern = malloc(count * sizeof(CGFloat)); - [source getLineDash:pattern count:&count phase:&phase]; - [destination setLineDash:pattern count:count phase:phase]; - free(pattern); -} - -void CopyBezierState(UIBezierPath *source, UIBezierPath *destination) -{ - destination.lineWidth = source.lineWidth; - destination.lineCapStyle = source.lineCapStyle; - destination.lineJoinStyle = source.lineJoinStyle; - destination.miterLimit = source.miterLimit; - destination.flatness = source.flatness; - destination.usesEvenOddFillRule = source.usesEvenOddFillRule; - CopyBezierDashes(source, destination); -} - -#pragma mark - Text -UIBezierPath *BezierPathFromString(NSString *string, UIFont *font) -{ - // Initialize path - UIBezierPath *path = [UIBezierPath bezierPath]; - if (!string.length) return path; - - // Create font ref - CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); - if (fontRef == NULL) - { - NSLog(@"Error retrieving CTFontRef from UIFont"); - return nil; - } - - // Create glyphs - CGGlyph *glyphs = malloc(sizeof(CGGlyph) * string.length); - const unichar *chars = (const unichar *)[string cStringUsingEncoding:NSUnicodeStringEncoding]; - BOOL success = CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, string.length); - if (!success) - { - NSLog(@"Error retrieving string glyphs"); - CFRelease(fontRef); - free(glyphs); - return nil; - } - - // Draw each char into path - for (int i = 0; i < string.length; i++) - { - CGGlyph glyph = glyphs[i]; - CGPathRef pathRef = CTFontCreatePathForGlyph(fontRef, glyph, NULL); - [path appendPath:[UIBezierPath bezierPathWithCGPath:pathRef]]; - CGPathRelease(pathRef); - CGSize size = [[string substringWithRange:NSMakeRange(i, 1)] sizeWithAttributes:@{NSFontAttributeName:font}]; - OffsetPath(path, CGSizeMake(-size.width, 0)); - } - - // Clean up - free(glyphs); - CFRelease(fontRef); - - // Math - MirrorPathVertically(path); - return path; -} - -UIBezierPath *BezierPathFromStringWithFontFace(NSString *string, NSString *fontFace) -{ - UIFont *font = [UIFont fontWithName:fontFace size:16]; - if (!font) - font = [UIFont systemFontOfSize:16]; - return BezierPathFromString(string, font); -} - -// Listing 8-1 -void MirrorPathVerticallyInContext(UIBezierPath *path) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGSize size = GetUIKitContextSize(); - CGRect contextRect = SizeMakeRect(size); - CGPoint center = RectGetCenter(contextRect); - - CGAffineTransform t = CGAffineTransformIdentity; - t = CGAffineTransformTranslate(t, center.x, center.y); - t = CGAffineTransformScale(t, 1, -1); - t = CGAffineTransformTranslate(t, -center.x, -center.y); - [path applyTransform:t]; -} - -void DrawAttributedStringIntoSubpath(UIBezierPath *path, NSAttributedString *attributedString, NSAttributedString **remainder) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - UIBezierPath *copy = [path safeCopy]; - MirrorPathVerticallyInContext(copy); - - CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attributedString); - CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedString.length), copy.CGPath, NULL); - - if (remainder) - { - CFRange range = CTFrameGetVisibleStringRange(theFrame); - NSInteger startLocation = range.location + range.length; - NSInteger extent = attributedString.length - startLocation; - NSAttributedString *substring = [attributedString attributedSubstringFromRange:NSMakeRange(startLocation, extent)]; - *remainder = substring; - } - - PushDraw(^{ - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - FlipContextVertically(GetUIKitContextSize()); - CTFrameDraw(theFrame, UIGraphicsGetCurrentContext()); - }); - - CFRelease(theFrame); - CFRelease(framesetter); -} - -void DrawAttributedStringInBezierPath(UIBezierPath *path, NSAttributedString *attributedString) -{ - DrawAttributedStringIntoSubpath(path, attributedString, nil); -} - -void DrawAttributedStringInBezierSubpaths(UIBezierPath *path, NSAttributedString *attributedString) -{ - NSAttributedString *string; - NSAttributedString *remainder = attributedString; - - for (UIBezierPath *subpath in path.subpaths) - { - string = remainder; - DrawAttributedStringIntoSubpath(subpath, string, &remainder); - if (remainder.length == 0) return; - } -} - -#pragma mark - Polygon Fun -UIBezierPath *BezierPolygon(NSUInteger numberOfSides) -{ - if (numberOfSides < 3) - { - NSLog(@"Error: Please supply at least 3 sides"); - return nil; - } - - CGRect destinationRect = CGRectMake(0, 0, 1, 1); - - UIBezierPath *path = [UIBezierPath bezierPath]; - CGPoint center = RectGetCenter(destinationRect); - CGFloat r = 0.5f; // radius - - BOOL firstPoint = YES; - for (int i = 0; i < (numberOfSides - 1); i++) - { - CGFloat theta = M_PI + i * TWO_PI / numberOfSides; - CGFloat dTheta = TWO_PI / numberOfSides; - - CGPoint p; - if (firstPoint) - { - p.x = center.x + r * sin(theta); - p.y = center.y + r * cos(theta); - [path moveToPoint:p]; - firstPoint = NO; - } - - p.x = center.x + r * sin(theta + dTheta); - p.y = center.y + r * cos(theta + dTheta); - [path addLineToPoint:p]; - } - - [path closePath]; - - return path; -} - -UIBezierPath *BezierInflectedShape(NSUInteger numberOfInflections, CGFloat percentInflection) -{ - if (numberOfInflections < 3) - { - NSLog(@"Error: Please supply at least 3 inflections"); - return nil; - } - - UIBezierPath *path = [UIBezierPath bezierPath]; - CGRect destinationRect = CGRectMake(0, 0, 1, 1); - CGPoint center = RectGetCenter(destinationRect); - CGFloat r = 0.5; - CGFloat rr = r * (1.0 + percentInflection); - - BOOL firstPoint = YES; - for (int i = 0; i < numberOfInflections; i++) - { - CGFloat theta = i * TWO_PI / numberOfInflections; - CGFloat dTheta = TWO_PI / numberOfInflections; - - if (firstPoint) - { - CGFloat xa = center.x + r * sin(theta); - CGFloat ya = center.y + r * cos(theta); - CGPoint pa = CGPointMake(xa, ya); - [path moveToPoint:pa]; - firstPoint = NO; - } - - CGFloat cp1x = center.x + rr * sin(theta + dTheta / 3); - CGFloat cp1y = center.y + rr * cos(theta + dTheta / 3); - CGPoint cp1 = CGPointMake(cp1x, cp1y); - - CGFloat cp2x = center.x + rr * sin(theta + 2 * dTheta / 3); - CGFloat cp2y = center.y + rr * cos(theta + 2 * dTheta / 3); - CGPoint cp2 = CGPointMake(cp2x, cp2y); - - CGFloat xb = center.x + r * sin(theta + dTheta); - CGFloat yb = center.y + r * cos(theta + dTheta); - CGPoint pb = CGPointMake(xb, yb); - - [path addCurveToPoint:pb controlPoint1:cp1 controlPoint2:cp2]; - } - - [path closePath]; - - return path; -} - -UIBezierPath *BezierStarShape(NSUInteger numberOfInflections, CGFloat percentInflection) -{ - if (numberOfInflections < 3) - { - NSLog(@"Error: Please supply at least 3 inflections"); - return nil; - } - - UIBezierPath *path = [UIBezierPath bezierPath]; - CGRect destinationRect = CGRectMake(0, 0, 1, 1); - CGPoint center = RectGetCenter(destinationRect); - CGFloat r = 0.5; - CGFloat rr = r * (1.0 + percentInflection); - - BOOL firstPoint = YES; - for (int i = 0; i < numberOfInflections; i++) - { - CGFloat theta = i * TWO_PI / numberOfInflections; - CGFloat dTheta = TWO_PI / numberOfInflections; - - if (firstPoint) - { - CGFloat xa = center.x + r * sin(theta); - CGFloat ya = center.y + r * cos(theta); - CGPoint pa = CGPointMake(xa, ya); - [path moveToPoint:pa]; - firstPoint = NO; - } - - CGFloat cp1x = center.x + rr * sin(theta + dTheta / 2); - CGFloat cp1y = center.y + rr * cos(theta + dTheta / 2); - CGPoint cp1 = CGPointMake(cp1x, cp1y); - - CGFloat xb = center.x + r * sin(theta + dTheta); - CGFloat yb = center.y + r * cos(theta + dTheta); - CGPoint pb = CGPointMake(xb, yb); - - [path addLineToPoint:cp1]; - [path addLineToPoint:pb]; - } - - [path closePath]; - - return path; -} - -#pragma mark - Shadows - -// Establish context shadow state -void SetShadow(UIColor *color, CGSize size, CGFloat blur) -{ - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - if (color) - CGContextSetShadowWithColor(context, size, blur, color.CGColor); - else - CGContextSetShadow(context, size, blur); -} - -// Draw *only* the shadow -void DrawShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - // Build shadow - PushDraw(^{ - SetShadow(color, CGSizeMake(size.width, size.height), blur); - [path.inverse addClip]; - [path fill:color]; - }); -} - -// Draw shadow inside shape -void DrawInnerShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - // Build shadow - PushDraw(^{ - SetShadow(color, CGSizeMake(size.width, size.height), blur); - [path addClip]; - [path.inverse fill:color]; - }); -} - -#pragma mark - Photoshop Style Effects -UIColor *ContrastColor(UIColor *color) -{ - if (CGColorSpaceGetNumberOfComponents(CGColorGetColorSpace(color.CGColor)) == 3) - { - CGFloat r, g, b, a; - [color getRed:&r green:&g blue:&b alpha:&a]; - CGFloat luminance = r * 0.2126f + g * 0.7152f + b * 0.0722f; - return (luminance > 0.5f) ? [UIColor blackColor] : [UIColor whiteColor]; - } - - CGFloat w, a; - [color getWhite:&w alpha:&a]; - return (w > 0.5f) ? [UIColor blackColor] : [UIColor whiteColor]; -} - -// Create 3d embossed effect -// Typically call with black color at 0.5 -void EmbossPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat blur) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - - UIColor *contrast = ContrastColor(color); - DrawInnerShadow(path, contrast, CGSizeMake(-radius, radius), blur); - DrawInnerShadow(path, color, CGSizeMake(radius, -radius), blur); -} - -// Half an emboss -void InnerBevel(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGFloat x = radius * sin(theta); - CGFloat y = radius * cos(theta); - - UIColor *shadowColor = [color colorWithAlphaComponent:0.5f]; - DrawInnerShadow(path, shadowColor, CGSizeMake(-x, y), 2); -} - -// I don't love this -void ExtrudePath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - CGFloat x = radius * sin(theta); - CGFloat y = radius * cos(theta); - DrawShadow(path, color, CGSizeMake(x, y), 0); -} - -// Typically call with black color at 0.5 -void BevelPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGFloat x = radius * sin(theta); - CGFloat y = radius * cos(theta); - DrawInnerShadow(path, color, CGSizeMake(-x, y), 2); - DrawShadow(path, color, CGSizeMake(x / 2 , -y / 2), 0); -} - -@implementation UIBezierPath (HandyUtilities) -#pragma mark - Bounds -- (CGPoint) center -{ - return PathBoundingCenter(self); -} - -- (CGRect) computedBounds -{ - return PathBoundingBox(self); -} - -- (CGRect) computedBoundsWithLineWidth -{ - return PathBoundingBoxWithLineWidth(self); -} - -#pragma mark - Stroking and Filling - -- (void) addDashes -{ - AddDashesToPath(self); -} - -- (void) addDashes: (NSArray *) pattern -{ - if (!pattern.count) return; - CGFloat *dashes = malloc(pattern.count * sizeof(CGFloat)); - for (int i = 0; i < pattern.count; i++) - dashes[i] = [pattern[i] floatValue]; - [self setLineDash:dashes count:pattern.count phase:0]; - free(dashes); -} - -- (void) applyPathPropertiesToContext -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGContextSetLineWidth(context, self.lineWidth); - CGContextSetLineCap(context, self.lineCapStyle); - CGContextSetLineJoin(context, self.lineJoinStyle); - CGContextSetMiterLimit(context, self.miterLimit); - CGContextSetFlatness(context, self.flatness); - - NSInteger count; - [self getLineDash:NULL count:&count phase:NULL]; - - CGFloat phase; - CGFloat *pattern = malloc(count * sizeof(CGFloat)); - [self getLineDash:pattern count:&count phase:&phase]; - CGContextSetLineDash(context, phase, pattern, count); - free(pattern); -} - -- (void) stroke: (CGFloat) width color: (UIColor *) color -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - PushDraw(^{ - if (color) [color setStroke]; - CGFloat holdWidth = self.lineWidth; - if (width > 0) - self.lineWidth = width; - [self stroke]; - self.lineWidth = holdWidth; - }); -} - -- (void) stroke: (CGFloat) width -{ - [self stroke:width color:nil]; -} - -- (void) strokeInside: (CGFloat) width color: (UIColor *) color -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - PushDraw(^{ - [self addClip]; - [self stroke:width * 2 color:color]; - }); -} - -- (void) strokeInside: (CGFloat) width -{ - [self strokeInside:width color:nil]; -} - -- (void) fill: (UIColor *) fillColor -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - PushDraw(^{ - if (fillColor) - [fillColor set]; - [self fill]; - }); -} - -- (void) fill: (UIColor *) fillColor withMode: (CGBlendMode) blendMode -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - PushDraw(^{ - CGContextSetBlendMode(context, blendMode); - [self fill:fillColor]; - }); -} - -- (void) fillWithNoise: (UIColor *) fillColor -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - [self fill:fillColor]; - [self fill:[NoiseColor() colorWithAlphaComponent:0.05f] withMode:kCGBlendModeScreen]; -} - -#pragma mark - Clippage -- (void) clipToPath -{ - [self addClip]; -} - -- (void) clipToStroke:(NSUInteger)width -{ - CGPathRef pathRef = CGPathCreateCopyByStrokingPath(self.CGPath, NULL, width, kCGLineCapButt, kCGLineJoinMiter, 4); - UIBezierPath *clipPath = [UIBezierPath bezierPathWithCGPath:pathRef]; - CGPathRelease(pathRef); - [clipPath addClip]; -} - -#pragma mark - Misc - -- (UIBezierPath *) safeCopy -{ - UIBezierPath *p = [UIBezierPath bezierPath]; - [p appendPath:self]; - CopyBezierState(self, p); - return p; -} -@end diff --git a/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h b/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h deleted file mode 100644 index d70a79177..000000000 --- a/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import "Bezier.h" - -// Construct path -UIBezierPath *BezierPathWithElements(NSArray *elements); -UIBezierPath *BezierPathWithPoints(NSArray *points); -UIBezierPath *InterpolatedPath(UIBezierPath *path); - -// Partial paths -UIBezierPath *CroppedPath(UIBezierPath *path, CGFloat percent); -UIBezierPath *PathFromPercentToPercent(UIBezierPath *path, CGFloat startPercent, CGFloat endPercent); - -/* - - UIBezierPath - Elements Category - - */ - -@interface UIBezierPath (Elements) - -@property (nonatomic, readonly) NSArray *elements; -@property (nonatomic, readonly) NSArray *subpaths; - -@property (nonatomic, readonly) NSArray *destinationPoints; -@property (nonatomic, readonly) NSArray *interpolatedPathPoints; - -@property (nonatomic, readonly) NSUInteger count; -- (id)objectAtIndexedSubscript:(NSUInteger)idx; - -@property (nonatomic, readonly) CGPoint center; -@property (nonatomic, readonly) CGRect calculatedBounds; - -@property (nonatomic, readonly) UIBezierPath *reversed; -@property (nonatomic, readonly) UIBezierPath *inverse; -@property (nonatomic, readonly) UIBezierPath *boundedInverse; - -@property (nonatomic, readonly) BOOL subpathIsClosed; -- (BOOL) closeSafely; - -// Measure length -@property (nonatomic, readonly) CGFloat pathLength; -- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope; - -// String Representations -- (void) showTheCode; -- (NSString *) stringValue; - -// -- Invert path to arbitrary rectangle -- (UIBezierPath *) inverseInRect: (CGRect) rect; -@end \ No newline at end of file diff --git a/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m b/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m deleted file mode 100644 index e951931d9..000000000 --- a/ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m +++ /dev/null @@ -1,519 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "UIBezierPath+Elements.h" -#import "BaseGeometry.h" - -#pragma mark - Construction - -// Return Bezier path built with the supplied elements -UIBezierPath *BezierPathWithElements(NSArray *elements) -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - for (BezierElement *element in elements) - [element addToPath:path]; - return path; -} - - -UIBezierPath *InterpolatedPath(UIBezierPath *path) -{ - return BezierPathWithPoints(path.interpolatedPathPoints); -} - -#define POINT(_X_) ([(NSValue *)points[_X_] CGPointValue]) - -// Pass array with NSValue'd CGRect points -UIBezierPath *BezierPathWithPoints(NSArray *points) -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - if (!points.count) - return path; - [path moveToPoint:POINT(0)]; - for (int i = 1; i < points.count; i++) - [path addLineToPoint:POINT(i)]; - [path closePath]; - return path; -} - -#pragma mark - Partial Path - -UIBezierPath *CroppedPath(UIBezierPath *path, CGFloat percent) -{ - NSArray *elements = path.elements; - if (elements.count == 0) return path; - - int targetCount = elements.count * percent; - NSArray *targetElements = [elements subarrayWithRange:NSMakeRange(0, targetCount)]; - UIBezierPath *outputPath = BezierPathWithElements(targetElements); - return outputPath; -} - -UIBezierPath *PathFromPercentToPercent(UIBezierPath *path, CGFloat startPercent, CGFloat endPercent) -{ - NSArray *elements = path.elements; - if (elements.count == 0) return path; - - int targetCount = elements.count * endPercent; - NSArray *targetElements = [elements subarrayWithRange:NSMakeRange(0, targetCount)]; - UIBezierPath *outputPath = BezierPathWithElements(targetElements); - return outputPath; -} - -#pragma mark - Bezier Elements Category - - -@implementation UIBezierPath (Elements) - -// Convert one element to BezierElement and save to array -void GetBezierElements(void *info, const CGPathElement *element) -{ - NSMutableArray *bezierElements = (__bridge NSMutableArray *)info; - if (element) - [bezierElements addObject:[BezierElement elementWithPathElement:*element]]; -} - -// Retrieve array of component elements -- (NSArray *) elements -{ - NSMutableArray *elements = [NSMutableArray array]; - CGPathApply(self.CGPath, (__bridge void *)elements, GetBezierElements); - return elements; -} - -#pragma mark - Subpaths -// Subpaths must be well defined -- (NSMutableArray *) subpaths -{ - NSMutableArray *results = [NSMutableArray array]; - UIBezierPath *current = nil; - NSArray *elements = self.elements; - - for (BezierElement *element in elements) - { - if (element.elementType == kCGPathElementCloseSubpath) - { - [current closePath]; - if (current) - [results addObject:current]; - current = nil; - continue; - } - - if (element.elementType == kCGPathElementMoveToPoint) - { - if (current) - [results addObject:current]; - - current = [UIBezierPath bezierPath]; - [current moveToPoint:element.point]; - continue; - } - - if (current) - [element addToPath:current]; - else - { - NSLog(@"Error: cannot add element to nil path: %@", element.stringValue); - continue; - } - } - - if (current) - [results addObject:current]; - - return results; -} - - -// Only collect those points that have destinations -- (NSArray *) destinationPoints -{ - NSMutableArray *array = [NSMutableArray array]; - NSArray *elements = self.elements; - - for (BezierElement *element in elements) - if (!POINT_IS_NULL(element.point)) - [array addObject:[NSValue valueWithCGPoint:element.point]]; - - return array; -} - -// Points and interpolated points -- (NSArray *) interpolatedPathPoints -{ - NSMutableArray *points = [NSMutableArray array]; - BezierElement *current = nil; - int overkill = 3; - for (BezierElement *element in self.elements) - { - switch (element.elementType) - { - case kCGPathElementMoveToPoint: - case kCGPathElementAddLineToPoint: - [points addObject:[NSValue valueWithCGPoint:element.point]]; - current = element; - break; - case kCGPathElementCloseSubpath: - current = nil; - break; - case kCGPathElementAddCurveToPoint: - { - for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++) - { - CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill); - CGPoint p = CubicBezierPoint(percent, current.point, element.controlPoint1, element.controlPoint2, element.point); - [points addObject:[NSValue valueWithCGPoint:p]]; - } - [points addObject:[NSValue valueWithCGPoint:element.point]]; - current = element; - break; - } - case kCGPathElementAddQuadCurveToPoint: - { - for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++) - { - CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill); - CGPoint p = QuadBezierPoint(percent, current.point, element.controlPoint1, element.point); - [points addObject:[NSValue valueWithCGPoint:p]]; - } - [points addObject:[NSValue valueWithCGPoint:element.point]]; - current = element; - break; - } - } - } - return points; -} - -#pragma mark - Array Access - -- (NSUInteger) count -{ - return self.elements.count; -} - -- (id)objectAtIndexedSubscript:(NSUInteger)idx -{ - NSArray *elements = self.elements; - if (idx >= elements.count) - return nil; - return elements[idx]; -} - -#pragma mark - Geometry Workaround -- (CGRect) calculatedBounds -{ - // Thank you Ryan Petrich - return CGPathGetPathBoundingBox(self.CGPath); -} - -// Return center of bounds -- (CGPoint) center -{ - return RectGetCenter(self.calculatedBounds); -} - -#pragma mark - Reversal Workaround -- (UIBezierPath *) reverseSubpath: (UIBezierPath *) subpath -{ - NSArray *elements = subpath.elements; - NSArray *reversedElements = [[elements reverseObjectEnumerator] allObjects]; - - UIBezierPath *newPath = [UIBezierPath bezierPath]; - CopyBezierState(self, newPath); - BOOL closesSubpath = NO; - - BezierElement *firstElement; - for (BezierElement *e in elements) - { - if (!POINT_IS_NULL(e.point)) - { - firstElement = e; - break; - } - } - - BezierElement *lastElement; - for (BezierElement *e in reversedElements) - { - if (!POINT_IS_NULL(e.point)) - { - lastElement = e; - break; - } - } - - BezierElement *element = [elements lastObject]; - if (element.elementType == kCGPathElementCloseSubpath) - { - if (firstElement) - [newPath moveToPoint:firstElement.point]; - - if (lastElement) - [newPath addLineToPoint:lastElement.point]; - - closesSubpath = YES; - } - else - { - [newPath moveToPoint:lastElement.point]; - } - - CFIndex i = 0; - for (BezierElement *element in reversedElements) - { - i++; - BezierElement *nextElement = nil; - BezierElement *workElement = [element copy]; - - if (element.elementType == kCGPathElementCloseSubpath) - continue; - - if (element == firstElement) - { - if (closesSubpath) [newPath closePath]; - continue; - } - - if (i < reversedElements.count) - { - nextElement = reversedElements[i]; - if (!POINT_IS_NULL(workElement.controlPoint2)) - { - CGPoint tmp = workElement.controlPoint1; - workElement.controlPoint1 = workElement.controlPoint2; - workElement.controlPoint2 = tmp; - } - workElement.point = nextElement.point; - } - - if (element.elementType == kCGPathElementMoveToPoint) - workElement.elementType = kCGPathElementAddLineToPoint; - - [workElement addToPath:newPath]; - } - - return newPath; - -} - -- (UIBezierPath *) reversed -{ - // [self bezierPathByReversingPath] seriously does not work the - // way you expect. Radars are filed. - - UIBezierPath *reversed = [UIBezierPath bezierPath]; - NSArray *reversedSubpaths = [[self.subpaths reverseObjectEnumerator] allObjects]; - - for (UIBezierPath *subpath in reversedSubpaths) - { - UIBezierPath *p = [self reverseSubpath:subpath]; - if (p) - [reversed appendPath:p]; - } - return reversed; -} - -#pragma mark - Closing -- (BOOL) subpathIsClosed -{ - NSArray *elements = self.elements; - - // A legal closed path must contain 3 elements - // move, add, close - if (elements.count < 3) - return NO; - - BezierElement *element = [elements lastObject]; - return element.elementType == kCGPathElementCloseSubpath; -} - -- (BOOL) closeSafely -{ - NSArray *elements = self.elements; - if (elements.count < 2) - return NO; - - BezierElement *element = [elements lastObject]; - if (element.elementType != kCGPathElementCloseSubpath) - { - [self closePath]; - return YES; - } - - return NO; -} - - -#pragma mark - Show the Code -- (void) showTheCode -{ - - printf("\n- (UIBezierPath *) buildBezierPath\n"); - printf("{\n"); - printf(" UIBezierPath *path = [UIBezierPath bezierPath];\n\n"); - - NSArray *elements = self.elements; - for (BezierElement *element in elements) - [element showTheCode]; - - printf(" return path;\n"); - printf("}\n\n"); -} - -- (NSString *) stringValue -{ - NSMutableString *string = [NSMutableString stringWithString:@"\n"]; - NSArray *elements = self.elements; - for (BezierElement *element in elements) - [string appendFormat:@"%@\n", element.stringValue]; - - return string; -} - -#pragma mark - Transformations -// Project point from native to dest -CGPoint adjustPoint(CGPoint p, CGRect native, CGRect dest) -{ - CGFloat scaleX = dest.size.width / native.size.width; - CGFloat scaleY = dest.size.height / native.size.height; - - CGPoint point = PointSubtractPoint(p, native.origin); - point.x *= scaleX; - point.y *= scaleY; - CGPoint destPoint = PointAddPoint(point, dest.origin); - - return destPoint; -} - -// Adjust points by applying block to each element -- (UIBezierPath *) adjustPathElementsWithBlock: (PathBlock) block -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - if (!block) - { - [path appendPath:self]; - return path; - } - - for (BezierElement *element in self.elements) - [[element elementByApplyingBlock:block] addToPath:path]; - - return path; -} - -// Apply transform -- (UIBezierPath *) pathApplyTransform: (CGAffineTransform) transform -{ - UIBezierPath *copy = [UIBezierPath bezierPath]; - [copy appendPath:self]; - - CGRect bounding = self.calculatedBounds; - CGPoint center = RectGetCenter(bounding); - CGAffineTransform t = CGAffineTransformIdentity; - t = CGAffineTransformTranslate(t, center.x, center.y); - t = CGAffineTransformConcat(transform, t); - t = CGAffineTransformTranslate(t, -center.x, -center.y); - [copy applyTransform:t]; - - return copy; -} - -- (CGFloat) pathLength -{ - NSArray *elements = self.elements; - CGPoint current = NULLPOINT; - CGPoint firstPoint = NULLPOINT; - float totalPointLength = 0.0f; - - for (BezierElement *element in elements) - { - totalPointLength += ElementDistanceFromPoint(element, current, firstPoint); - - if (element.elementType == kCGPathElementMoveToPoint) - firstPoint = element.point; - else if (element.elementType == kCGPathElementCloseSubpath) - firstPoint = NULLPOINT; - - if (element.elementType != kCGPathElementCloseSubpath) - current = element.point; - } - - return totalPointLength; -} - - -// Retrieve the point and slope at a given percent offset -- This is expensive -- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope -{ - NSArray *elements = self.elements; - - if (percent == 0.0f) - { - BezierElement *first = [elements objectAtIndex:0]; - return first.point; - } - - float pathLength = self.pathLength; - float totalDistance = 0.0f; - - CGPoint current = NULLPOINT; - CGPoint firstPoint = NULLPOINT; - - for (BezierElement *element in elements) - { - float distance = ElementDistanceFromPoint(element, current, firstPoint); - CGFloat proposedTotalDistance = totalDistance + distance; - CGFloat proposedPercent = proposedTotalDistance / pathLength; - - if (proposedPercent < percent) - { - // consume and continue - totalDistance = proposedTotalDistance; - - if (element.elementType == kCGPathElementMoveToPoint) - firstPoint = element.point; - - current = element.point; - - continue; - } - - // What percent between p1 and p2? - CGFloat currentPercent = totalDistance / pathLength; - CGFloat dPercent = percent - currentPercent; - CGFloat percentDistance = dPercent * pathLength; - CGFloat targetPercent = percentDistance / distance; - - // Return result - CGPoint point = InterpolatePointFromElement(element, current, firstPoint, targetPercent, slope); - return point; - } - - return NULLPOINT; -} - -#pragma mark - Inverses -- (UIBezierPath *) inverseInRect: (CGRect) rect -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - CopyBezierState(self, path); - [path appendPath:self]; - [path appendPath:[UIBezierPath bezierPathWithRect:rect]]; - path.usesEvenOddFillRule = YES; - return path; -} - -- (UIBezierPath *) inverse -{ - return [self inverseInRect:CGRectInfinite]; -} - -- (UIBezierPath *) boundedInverse -{ - return [self inverseInRect:self.bounds]; -} -@end diff --git a/ios/QuartzBookPack/Drawing/Drawing-Block.h b/ios/QuartzBookPack/Drawing/Drawing-Block.h deleted file mode 100644 index 0f7efb8bf..000000000 --- a/ios/QuartzBookPack/Drawing/Drawing-Block.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import - -typedef void (^DrawingBlock)(CGRect bounds); -typedef void (^DrawingStateBlock)(); -void PushDraw(DrawingStateBlock block); -void PushLayerDraw(DrawingStateBlock block); - - -// Image -UIImage *ImageWithBlock(DrawingBlock block, CGSize size); -UIImage *DrawIntoImage(CGSize size, DrawingStateBlock block); - - -// Blurring -void DrawAndBlur(CGFloat radius, DrawingStateBlock block); diff --git a/ios/QuartzBookPack/Drawing/Drawing-Block.m b/ios/QuartzBookPack/Drawing/Drawing-Block.m deleted file mode 100644 index 0fcaa8c74..000000000 --- a/ios/QuartzBookPack/Drawing/Drawing-Block.m +++ /dev/null @@ -1,71 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "Drawing-Block.h" -#import "Utility.h" - -#pragma mark - Drawing -UIImage *ImageWithBlock(DrawingBlock block, CGSize size) -{ - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - if (block) block((CGRect){.size = size}); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - -void PushDraw(DrawingStateBlock block) -{ - if (!block) return; // nothing to do - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGContextSaveGState(context); - block(); - CGContextRestoreGState(context); -} - -// Improve performance by pre-clipping context -// before beginning layer drawing -void PushLayerDraw(DrawingStateBlock block) -{ - if (!block) return; // nothing to do - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGContextBeginTransparencyLayer(context, NULL); - block(); - CGContextEndTransparencyLayer(context); -} - -UIImage *DrawIntoImage(CGSize size, DrawingStateBlock block) -{ - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - if (block) block(); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - - -#define DEBUG_IMAGE(_IMAGE_, _NAME_) [UIImagePNGRepresentation(_IMAGE_) writeToFile:[NSString stringWithFormat:@"/Users/ericasadun/Desktop/%@.png", _NAME_] atomically:YES] - -// Create a blurred drawing group -// Listing 7-4 -void DrawAndBlur(CGFloat radius, DrawingStateBlock block) -{ - if (!block) return; // nothing to do - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - // Draw and blur the image - UIImage *baseImage = DrawIntoImage(GetUIKitContextSize(), block); - UIImage *blurred = GaussianBlurImage(baseImage, radius); - [blurred drawAtPoint:CGPointZero]; -} \ No newline at end of file diff --git a/ios/QuartzBookPack/Drawing/Drawing-Gradient.h b/ios/QuartzBookPack/Drawing/Drawing-Gradient.h deleted file mode 100644 index 41d29c38e..000000000 --- a/ios/QuartzBookPack/Drawing/Drawing-Gradient.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import "BezierFunctions.h" - -#define COLOR_LEVEL(_selector_, _alpha_) [([UIColor _selector_])colorWithAlphaComponent:_alpha_] -#define WHITE_LEVEL(_amt_, _alpha_) [UIColor colorWithWhite:(_amt_) alpha:(_alpha_)] - -// Gradient drawing styles -#define LIMIT_GRADIENT_EXTENT 0 -#define BEFORE_START kCGGradientDrawsBeforeStartLocation -#define AFTER_END kCGGradientDrawsAfterEndLocation -#define KEEP_DRAWING kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation - -typedef __attribute__((NSObject)) CGGradientRef GradientObject; - -@interface Gradient : NSObject -@property (nonatomic, readonly) CGGradientRef gradient; -+ (instancetype) gradientWithColors: (NSArray *) colors locations: (NSArray *) locations; -+ (instancetype) gradientFrom: (UIColor *) color1 to: (UIColor *) color2; - -+ (instancetype) rainbow; -+ (instancetype) linearGloss:(UIColor *) color; -+ (instancetype) gradientUsingInterpolationBlock: (InterpolationBlock) block between: (UIColor *) c1 and: (UIColor *) c2; -+ (instancetype) easeInGradientBetween: (UIColor *) c1 and:(UIColor *) c2; -+ (instancetype) easeInOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2; -+ (instancetype) easeOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2; - -- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 style: (int) mask; -- (void) drawRadialFrom:(CGPoint) p1 toPoint: (CGPoint) p2 radii: (CGPoint) radii style: (int) mask; - -- (void) drawTopToBottom: (CGRect) rect; -- (void) drawBottomToTop: (CGRect) rect; -- (void) drawLeftToRight: (CGRect) rect; -- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2; -- (void) drawAlongAngle: (CGFloat) angle in:(CGRect) rect; - -- (void) drawBasicRadial: (CGRect) rect; -- (void) drawRadialFrom: (CGPoint) p1 toPoint: (CGPoint) p2; -@end; diff --git a/ios/QuartzBookPack/Drawing/Drawing-Gradient.m b/ios/QuartzBookPack/Drawing/Drawing-Gradient.m deleted file mode 100644 index 9e85897e5..000000000 --- a/ios/QuartzBookPack/Drawing/Drawing-Gradient.m +++ /dev/null @@ -1,256 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "Drawing-Gradient.h" -#import "Utility.h" - -@interface Gradient () -@property (nonatomic, strong) GradientObject storedGradient; -@end - -@implementation Gradient - -#pragma mark - Internal -- (CGGradientRef) gradient -{ - return _storedGradient; -} - -#pragma mark - Convenience Creation -+ (instancetype) gradientWithColors: (NSArray *) colorsArray locations: (NSArray *) locationArray -{ - if (!colorsArray) COMPLAIN_AND_BAIL_NIL(@"Missing colors array", nil); - if (!locationArray) COMPLAIN_AND_BAIL_NIL(@"Missing location array", nil); - - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - if (space == NULL) - { - NSLog(@"Error: Unable to create device RGB color space"); - return nil; - } - - // Convert locations to CGFloat * - CGFloat locations[locationArray.count]; - for (int i = 0; i < locationArray.count; i++) - locations[i] = [locationArray[i] floatValue]; - - // Convert colors to (id) CGColorRef - NSMutableArray *colorRefArray = [NSMutableArray array]; - for (UIColor *color in colorsArray) - [colorRefArray addObject:(id)color.CGColor]; - - CGGradientRef gradientRef = CGGradientCreateWithColors(space, (__bridge CFArrayRef) colorRefArray, locations); - CGColorSpaceRelease(space); - - if (gradientRef == NULL) - { - NSLog(@"Error: Unable to construct CGGradientRef"); - return nil; - } - - Gradient *gradient = [[self alloc] init]; - gradient.storedGradient = gradientRef; - CGGradientRelease(gradientRef); - - return gradient; -} - -+ (instancetype) gradientFrom: (UIColor *) color1 to: (UIColor *) color2 -{ - return [self gradientWithColors:@[color1, color2] locations:@[@(0.0f), @(1.0f)]]; -} - -#pragma mark - Linear - -- (void) drawRadialFrom:(CGPoint) p1 toPoint: (CGPoint) p2 radii: (CGPoint) radii style: (int) mask -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGContextDrawRadialGradient(context, self.gradient, p1, radii.x, p2, radii.y, mask); -} - -- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 style: (int) mask -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGContextDrawLinearGradient(context, self.gradient, p1, p2, mask); -} - -- (void) drawLeftToRight: (CGRect) rect -{ - CGPoint p1 = RectGetMidLeft(rect); - CGPoint p2 = RectGetMidRight(rect); - [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; -} - -- (void) drawTopToBottom: (CGRect) rect -{ - CGPoint p1 = RectGetMidTop(rect); - CGPoint p2 = RectGetMidBottom(rect); - [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; -} - -- (void) drawBottomToTop:(CGRect)rect -{ - CGPoint p1 = RectGetMidBottom(rect); - CGPoint p2 = RectGetMidTop(rect); - [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; -} - -- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 -{ - [self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING]; -} - -- (void) drawAlongAngle: (CGFloat) theta in:(CGRect) rect -{ - CGPoint center = RectGetCenter(rect); - CGFloat r = PointDistanceFromPoint(center, RectGetTopRight(rect)); - - CGFloat phi = theta + M_PI; - if (phi > TWO_PI) - phi -= TWO_PI; - - CGFloat dx1 = r * sin(theta); - CGFloat dy1 = r * cos(theta); - CGFloat dx2 = r * sin(phi); - CGFloat dy2 = r * cos(phi); - - CGPoint p1 = CGPointMake(center.x + dx1, center.y + dy1); - CGPoint p2 = CGPointMake(center.x + dx2, center.y + dy2); - [self drawFrom:p1 toPoint:p2]; -} - -#pragma mark - Radial -- (void) drawBasicRadial: (CGRect) rect -{ - CGPoint p1 = RectGetCenter(rect); - CGFloat r = CGRectGetWidth(rect) / 2; - [self drawRadialFrom:p1 toPoint:p1 radii:CGPointMake(0, r) style:KEEP_DRAWING]; -} - -- (void) drawRadialFrom: (CGPoint) p1 toPoint: (CGPoint) p2; -{ - [self drawRadialFrom:p1 toPoint:p1 radii:CGPointMake(0, PointDistanceFromPoint(p1, p2)) style:KEEP_DRAWING]; -} - -#pragma mark - Prebuilt -+ (instancetype) rainbow -{ - NSMutableArray *colors = [NSMutableArray array]; - NSMutableArray *locations = [NSMutableArray array]; - int n = 24; - for (int i = 0; i <= n; i++) - { - CGFloat percent = (CGFloat) i / (CGFloat) n; - CGFloat colorDistance = percent * (CGFloat) (n - 1) / (CGFloat) n; - UIColor *color = [UIColor colorWithHue:colorDistance saturation:1 brightness:1 alpha:1]; - [colors addObject:color]; - [locations addObject:@(percent)]; - } - - return [Gradient gradientWithColors:colors locations:locations]; -} - -+ (instancetype) linearGloss:(UIColor *) color -{ - CGFloat r, g, b, a; - [color getRed:&r green:&g blue:&b alpha:&a]; - - CGFloat l = (0.299f * r + 0.587f * g + 0.114f * b); - CGFloat gloss = pow(l, 0.2) * 0.5; - - CGFloat h, s, v; - [color getHue:&h saturation:&s brightness:&v alpha:NULL]; - s = fminf(s, 0.2f); - - // Rotate by 0.6 PI - CGFloat rHue = ((h < 0.95) && (h > 0.7)) ? 0.67 : 0.17; - CGFloat phi = rHue * M_PI * 2; - CGFloat theta = h * M_PI; - - // Interpolate distance - CGFloat dTheta = (theta - phi); - while (dTheta < 0) dTheta += M_PI * 2; - while (dTheta > 2 * M_PI) dTheta -= M_PI_2; - CGFloat factor = 0.7 + 0.3 * cosf(dTheta); - - // Build highlight colors - UIColor *c1 = [UIColor colorWithHue:h * factor + (1 - factor) * rHue saturation:s brightness:v * factor + (1 - factor) alpha:gloss]; - UIColor *c2 = [c1 colorWithAlphaComponent:0]; - - // Build gradient - NSArray *colors = @[WHITE_LEVEL(1, gloss), WHITE_LEVEL(1, 0.2), c2, c1]; - NSArray *locations = @[@(0.0), @(0.5), @(0.5), @(1)]; - - return [Gradient gradientWithColors:colors locations:locations]; -} - -UIColor *InterpolateBetweenColors(UIColor *c1, UIColor *c2, CGFloat amt) -{ - CGFloat r1, g1, b1, a1; - CGFloat r2, g2, b2, a2; - - if (CGColorGetNumberOfComponents(c1.CGColor) == 4) - [c1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1]; - else - { - [c1 getWhite:&r1 alpha:&a1]; - g1 = r1; b1 = r1; - } - - if (CGColorGetNumberOfComponents(c2.CGColor) == 4) - [c2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2]; - else - { - [c2 getWhite:&r2 alpha:&a2]; - g2 = r2; b2 = r2; - } - - CGFloat r = (r2 * amt) + (r1 * (1.0 - amt)); - CGFloat g = (g2 * amt) + (g1 * (1.0 - amt)); - CGFloat b = (b2 * amt) + (b1 * (1.0 - amt)); - CGFloat a = (a2 * amt) + (a1 * (1.0 - amt)); - return [UIColor colorWithRed:r green:g blue:b alpha:a]; -} - - -+ (instancetype) gradientUsingInterpolationBlock: (InterpolationBlock) block between: (UIColor *) c1 and: (UIColor *) c2; -{ - if (!block) - COMPLAIN_AND_BAIL_NIL(@"Must pass interpolation block", nil); - - NSMutableArray *colors = [NSMutableArray array]; - NSMutableArray *locations = [NSMutableArray array]; - int numberOfSamples = 24; - for (int i = 0; i <= numberOfSamples; i++) - { - CGFloat amt = (CGFloat) i / (CGFloat) numberOfSamples; - CGFloat percentage = Clamp(block(amt), 0.0, 1.0); - [colors addObject:InterpolateBetweenColors(c1, c2, percentage)]; - [locations addObject:@(amt)]; - } - - return [Gradient gradientWithColors:colors locations:locations]; -} - -+ (instancetype) easeInGradientBetween: (UIColor *) c1 and:(UIColor *) c2 -{ - return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseIn(percent, 3);} between:c1 and:c2]; -} - -+ (instancetype) easeInOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2 -{ - return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseInOut(percent, 3);} between:c1 and:c2]; -} - -+ (instancetype) easeOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2 -{ - return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseOut(percent, 3);} between:c1 and:c2]; -} -@end diff --git a/ios/QuartzBookPack/Drawing/Drawing-Util.h b/ios/QuartzBookPack/Drawing/Drawing-Util.h deleted file mode 100644 index fd6ea40af..000000000 --- a/ios/QuartzBookPack/Drawing/Drawing-Util.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import "Drawing-Gradient.h" - -UIColor *ScaleColorBrightness(UIColor *color, CGFloat amount); - -void DrawStrokedShadowedShape(UIBezierPath *path, UIColor *baseColor, CGRect dest); -void DrawStrokedShadowedText(NSString *string, NSString *fontFace, UIColor *baseColor, CGRect dest); - -void DrawIndentedPath(UIBezierPath *path, UIColor *primary, CGRect rect); -void DrawIndentedText(NSString *string, NSString *fontFace, UIColor *primary, CGRect rect); - -void DrawGradientOverTexture(UIBezierPath *path, UIImage *texture, Gradient *gradient, CGFloat alpha); -void DrawBottomGlow(UIBezierPath *path, UIColor *color, CGFloat percent); -void DrawIconTopLight(UIBezierPath *path, CGFloat p); - -CGSize GetUIKitContextSize(); -UIImage *GradientMaskedReflectionImage(UIImage *sourceImage); -void DrawGradientMaskedReflection(UIImage *image, CGRect rect);; -void ApplyMaskToContext(UIImage *mask); diff --git a/ios/QuartzBookPack/Drawing/Drawing-Util.m b/ios/QuartzBookPack/Drawing/Drawing-Util.m deleted file mode 100644 index 46005dd7b..000000000 --- a/ios/QuartzBookPack/Drawing/Drawing-Util.m +++ /dev/null @@ -1,241 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "Drawing-Util.h" -#import "Utility.h" - -UIColor *ScaleColorBrightness(UIColor *color, CGFloat amount) -{ - CGFloat h, s, v, a; - [color getHue:&h saturation:&s brightness:&v alpha:&a]; - CGFloat v1 = Clamp(v * amount, 0, 1); - return [UIColor colorWithHue:h saturation:s brightness:v1 alpha:a]; -} - -void DrawStrokedShadowedShape(UIBezierPath *path, UIColor *baseColor, CGRect dest) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (!context) COMPLAIN_AND_BAIL(@"No context to draw to", nil); - - PushDraw(^{ - CGContextSetShadow(context, CGSizeMake(4, 4), 4); - - PushLayerDraw(^{ - - // Draw letter gradient (to half brightness) - PushDraw(^{ - Gradient *innerGradient = [Gradient gradientFrom:baseColor to:ScaleColorBrightness(baseColor, 0.5)]; - [path addClip]; - [innerGradient drawTopToBottom:path.bounds]; - }); - - // Add the inner shadow with darker color - PushDraw(^{ - CGContextSetBlendMode(context, kCGBlendModeMultiply); - DrawInnerShadow(path, ScaleColorBrightness(baseColor, 0.3), CGSizeMake(0, -2), 2); - }); - - // Stroke with reversed gray gradient - PushDraw(^{ - [path clipToStroke:6]; - [path.inverse addClip]; - Gradient *grayGradient = [Gradient gradientFrom:WHITE_LEVEL(0.0, 1) to:WHITE_LEVEL(0.5, 1)]; - [grayGradient drawTopToBottom:dest]; - }); - - }); - }); -} - -void DrawStrokedShadowedText(NSString *string, NSString *fontFace, UIColor *baseColor, CGRect dest) -{ - // Create text path - UIBezierPath *text = BezierPathFromStringWithFontFace(string, fontFace); - FitPathToRect(text, dest); - DrawStrokedShadowedShape(text, baseColor, dest); -} - - -void DrawIndentedPath(UIBezierPath *path, UIColor *primary, CGRect rect) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (!context) COMPLAIN_AND_BAIL(@"No context to draw to", nil); - - PushDraw(^{ - CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeMultiply); - DrawInnerShadow(path, WHITE_LEVEL(0, 0.4), CGSizeMake(0, 2), 1); - }); - - DrawShadow(path, WHITE_LEVEL(1, 0.5), CGSizeMake(0, 2), 1); - BevelPath(path, WHITE_LEVEL(0, 0.4), 2, 0); - - PushDraw(^{ - [path addClip]; - CGContextSetAlpha(UIGraphicsGetCurrentContext(), 0.3); - - UIColor *secondary = ScaleColorBrightness(primary, 0.3); - Gradient *gradient = [Gradient gradientFrom:primary to:secondary]; - [gradient drawBottomToTop:path.bounds]; - }); - -} - -void DrawIndentedText(NSString *string, NSString *fontFace, UIColor *primary, CGRect rect) -{ - UIBezierPath *letterPath = BezierPathFromStringWithFontFace(string, fontFace); - // RotatePath(letterPath, RadiansFromDegrees(-15)); - FitPathToRect(letterPath, rect); - DrawIndentedPath(letterPath, primary, rect); -} - -void DrawGradientOverTexture(UIBezierPath *path, UIImage *texture, Gradient *gradient, CGFloat alpha) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!texture) COMPLAIN_AND_BAIL(@"Texture cannot be nil", nil); - if (!gradient) COMPLAIN_AND_BAIL(@"Gradient cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGRect rect = path.bounds; - PushDraw(^{ - CGContextSetAlpha(context, alpha); - [path addClip]; - PushLayerDraw(^{ - [texture drawInRect:rect]; - CGContextSetBlendMode(context, kCGBlendModeColor); - [gradient drawTopToBottom:rect]; - }); - }); -} - -void DrawBottomGlow(UIBezierPath *path, UIColor *color, CGFloat percent) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGRect rect = path.calculatedBounds; - CGPoint h1 = RectGetPointAtPercents(rect, 0.5f, 1.0f); - CGPoint h2 = RectGetPointAtPercents(rect, 0.5f, 1.0f - percent); - - Gradient *gradient = [Gradient easeInOutGradientBetween:color and:[color colorWithAlphaComponent:0.0f]]; - - PushDraw(^{ - [path addClip]; - [gradient drawFrom:h1 toPoint:h2]; - }); -} - -void DrawIconTopLight(UIBezierPath *path, CGFloat p) -{ - if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - CGFloat percent = 1.0f - p; - CGRect rect = path.bounds; - CGRect offset = rect; - offset.origin.y -= percent * offset.size.height; - offset = CGRectInset(offset, -offset.size.width * 0.3f, 0); - - UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:offset]; - Gradient *gradient = [Gradient gradientFrom:WHITE_LEVEL(1, 0.0) to: WHITE_LEVEL(1, 0.5)]; - - PushDraw(^{ - [path addClip]; - [ovalPath addClip]; - - // Draw gradient - CGPoint p1 = RectGetPointAtPercents(rect, 0.5, 0.0); - CGPoint p2 = RectGetPointAtPercents(ovalPath.bounds, 0.5, 1); - [gradient drawFrom:p1 toPoint:p2]; - }); -} - -CGSize GetQuartzContextSize() -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) return CGSizeZero; - return CGSizeMake(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context)); -} - -// Listing 7-4 -CGSize GetUIKitContextSize() -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) return CGSizeZero; - CGSize size = CGSizeMake(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context)); - CGFloat scale = [UIScreen mainScreen].scale; - return CGSizeMake(size.width / scale, size.height / scale); -} - -void ApplyMaskToContext(UIImage *mask) -{ - if (!mask) COMPLAIN_AND_BAIL(@"Mask cannot be nil", nil); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to apply mask to", nil); - - // Ensure that mask is grayscale - UIImage *gray = GrayscaleVersionOfImage(mask); - CGSize contextSize = GetUIKitContextSize(); - - // Clipping takes place in Quartz space, so flip before applying - FlipContextVertically(contextSize); - CGContextClipToMask(context, SizeMakeRect(contextSize), gray.CGImage); - FlipContextVertically(contextSize); -} - -UIImage *ApplyMaskToImage(UIImage *image, UIImage *mask) -{ - if (!image) COMPLAIN_AND_BAIL_NIL(@"Image cannot be nil", nil); - if (!mask) COMPLAIN_AND_BAIL_NIL(@"Mask cannot be nil", nil); - - UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0); - ApplyMaskToContext(mask); - [image drawInRect:SizeMakeRect(image.size)]; - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return result; -} - -UIImage *GradientImage(CGSize size, UIColor *c1, UIColor *c2) -{ - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - - Gradient *gradient = [Gradient gradientFrom:c1 to:c2]; - [gradient drawTopToBottom:SizeMakeRect(size)]; - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - -UIImage *GradientMaskedReflectionImage(UIImage *sourceImage) -{ - UIImage *mirror = ImageMirroredVertically(sourceImage); - UIImage *gradImage = GrayscaleVersionOfImage(GradientImage(sourceImage.size, WHITE_LEVEL(1, 0.5), WHITE_LEVEL(0, 0.5))); - UIImage *masked = ApplyMaskToImage(mirror, gradImage); - return masked; -} - -// Listing 7-5 -void DrawGradientMaskedReflection(UIImage *image, CGRect rect) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - UIImage *gradient = GradientImage(rect.size, WHITE_LEVEL(1, 0.5), WHITE_LEVEL(1, 0.0)); - PushDraw(^{ - CGContextTranslateCTM(context, 0, rect.origin.y); - FlipContextVertically(rect.size); - CGContextTranslateCTM(context, 0, -rect.origin.y); - CGContextClipToMask(context, rect, gradient.CGImage); - [image drawInRect:rect]; - }); -} - - diff --git a/ios/QuartzBookPack/Geometry/BaseGeometry.h b/ios/QuartzBookPack/Geometry/BaseGeometry.h deleted file mode 100644 index b52f6bea5..000000000 --- a/ios/QuartzBookPack/Geometry/BaseGeometry.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ -#import -#import -// Just because -#define TWO_PI (2 * M_PI) - -// Undefined point -#define NULLPOINT CGRectNull.origin -#define POINT_IS_NULL(_POINT_) CGPointEqualToPoint(_POINT_, NULLPOINT) - -// General -#define RECTSTRING(_aRect_) NSStringFromCGRect(_aRect_) -#define POINTSTRING(_aPoint_) NSStringFromCGPoint(_aPoint_) -#define SIZESTRING(_aSize_) NSStringFromCGSize(_aSize_) - -#define RECT_WITH_SIZE(_SIZE_) (CGRect){.size = _SIZE_} -#define RECT_WITH_POINT(_POINT_) (CGRect){.origin = _POINT_} - -// Conversion -CGFloat DegreesFromRadians(CGFloat radians); -CGFloat RadiansFromDegrees(CGFloat degrees); - -// Clamping -CGFloat Clamp(CGFloat a, CGFloat min, CGFloat max); -CGPoint ClampToRect(CGPoint pt, CGRect rect); - -// General Geometry -CGPoint RectGetCenter(CGRect rect); -CGFloat PointDistanceFromPoint(CGPoint p1, CGPoint p2); - -// Construction -CGRect RectMakeRect(CGPoint origin, CGSize size); -CGRect SizeMakeRect(CGSize size); -CGRect PointsMakeRect(CGPoint p1, CGPoint p2); -CGRect OriginMakeRect(CGPoint origin); -CGRect RectAroundCenter(CGPoint center, CGSize size); -CGRect RectCenteredInRect(CGRect rect, CGRect mainRect); - -// Point Locations -CGPoint RectGetPointAtPercents(CGRect rect, CGFloat xPercent, CGFloat yPercent); -CGPoint PointAddPoint(CGPoint p1, CGPoint p2); -CGPoint PointSubtractPoint(CGPoint p1, CGPoint p2); - -// Cardinal Points -CGPoint RectGetTopLeft(CGRect rect); -CGPoint RectGetTopRight(CGRect rect); -CGPoint RectGetBottomLeft(CGRect rect); -CGPoint RectGetBottomRight(CGRect rect); -CGPoint RectGetMidTop(CGRect rect); -CGPoint RectGetMidBottom(CGRect rect); -CGPoint RectGetMidLeft(CGRect rect); -CGPoint RectGetMidRight(CGRect rect); - -// Aspect and Fitting -CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor); -CGSize RectGetScale(CGRect sourceRect, CGRect destRect); -CGFloat AspectScaleFill(CGSize sourceSize, CGRect destRect); -CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect); -CGRect RectByFittingRect(CGRect sourceRect, CGRect destinationRect); -CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect); -CGRect RectInsetByPercent(CGRect rect, CGFloat percent); - -// Transforms -CGFloat TransformGetXScale(CGAffineTransform t); -CGFloat TransformGetYScale(CGAffineTransform t); -CGFloat TransformGetRotation(CGAffineTransform t); diff --git a/ios/QuartzBookPack/Geometry/BaseGeometry.m b/ios/QuartzBookPack/Geometry/BaseGeometry.m deleted file mode 100644 index 6e8b655d0..000000000 --- a/ios/QuartzBookPack/Geometry/BaseGeometry.m +++ /dev/null @@ -1,242 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "BaseGeometry.h" - -#pragma mark - Conversion -// Degrees from radians -CGFloat DegreesFromRadians(CGFloat radians) -{ - return radians * 180.0f / M_PI; -} - -// Radians from degrees -CGFloat RadiansFromDegrees(CGFloat degrees) -{ - return degrees * M_PI / 180.0f; -} - -#pragma mark - Clamp -CGFloat Clamp(CGFloat a, CGFloat min, CGFloat max) -{ - return fmin(fmax(min, a), max); -} - -CGPoint ClampToRect(CGPoint pt, CGRect rect) -{ - CGFloat x = Clamp(pt.x, CGRectGetMinX(rect), CGRectGetMaxX(rect)); - CGFloat y = Clamp(pt.y, CGRectGetMinY(rect), CGRectGetMaxY(rect)); - return CGPointMake(x, y); -} - - -#pragma mark - General Geometry -CGPoint RectGetCenter(CGRect rect) -{ - return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); -} - -CGFloat PointDistanceFromPoint(CGPoint p1, CGPoint p2) -{ - CGFloat dx = p2.x - p1.x; - CGFloat dy = p2.y - p1.y; - - return sqrt(dx*dx + dy*dy); -} - -CGPoint RectGetPointAtPercents(CGRect rect, CGFloat xPercent, CGFloat yPercent) -{ - CGFloat dx = xPercent * rect.size.width; - CGFloat dy = yPercent * rect.size.height; - return CGPointMake(rect.origin.x + dx, rect.origin.y + dy); -} - -#pragma mark - Rectangle Construction -CGRect RectMakeRect(CGPoint origin, CGSize size) -{ - return (CGRect){.origin = origin, .size = size}; -} - -CGRect SizeMakeRect(CGSize size) -{ - return (CGRect){.size = size}; -} - -CGRect PointsMakeRect(CGPoint p1, CGPoint p2) -{ - CGRect rect = CGRectMake(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y); - return CGRectStandardize(rect); -} - -CGRect OriginMakeRect(CGPoint origin) -{ - return (CGRect){.origin = origin}; -} - -CGRect RectAroundCenter(CGPoint center, CGSize size) -{ - CGFloat halfWidth = size.width / 2.0f; - CGFloat halfHeight = size.height / 2.0f; - - return CGRectMake(center.x - halfWidth, center.y - halfHeight, size.width, size.height); -} - -CGRect RectCenteredInRect(CGRect rect, CGRect mainRect) -{ - CGFloat dx = CGRectGetMidX(mainRect)-CGRectGetMidX(rect); - CGFloat dy = CGRectGetMidY(mainRect)-CGRectGetMidY(rect); - return CGRectOffset(rect, dx, dy); -} - -#pragma mark - Point Location -CGPoint PointAddPoint(CGPoint p1, CGPoint p2) -{ - return CGPointMake(p1.x + p2.x, p1.y + p2.y); -} - -CGPoint PointSubtractPoint(CGPoint p1, CGPoint p2) -{ - return CGPointMake(p1.x - p2.x, p1.y - p2.y); -} - -#pragma mark - Cardinal Points -CGPoint RectGetTopLeft(CGRect rect) -{ - return CGPointMake( - CGRectGetMinX(rect), - CGRectGetMinY(rect) - ); -} - -CGPoint RectGetTopRight(CGRect rect) -{ - return CGPointMake( - CGRectGetMaxX(rect), - CGRectGetMinY(rect) - ); -} - -CGPoint RectGetBottomLeft(CGRect rect) -{ - return CGPointMake( - CGRectGetMinX(rect), - CGRectGetMaxY(rect) - ); -} - -CGPoint RectGetBottomRight(CGRect rect) -{ - return CGPointMake( - CGRectGetMaxX(rect), - CGRectGetMaxY(rect) - ); -} - -CGPoint RectGetMidTop(CGRect rect) -{ - return CGPointMake( - CGRectGetMidX(rect), - CGRectGetMinY(rect) - ); -} - -CGPoint RectGetMidBottom(CGRect rect) -{ - return CGPointMake( - CGRectGetMidX(rect), - CGRectGetMaxY(rect) - ); -} - -CGPoint RectGetMidLeft(CGRect rect) -{ - return CGPointMake( - CGRectGetMinX(rect), - CGRectGetMidY(rect) - ); -} - -CGPoint RectGetMidRight(CGRect rect) -{ - return CGPointMake( - CGRectGetMaxX(rect), - CGRectGetMidY(rect) - ); -} - -#pragma mark - Aspect and Fitting -CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor) -{ - return CGSizeMake(aSize.width * factor, aSize.height * factor); -} - -CGSize RectGetScale(CGRect sourceRect, CGRect destRect) -{ - CGSize sourceSize = sourceRect.size; - CGSize destSize = destRect.size; - - CGFloat scaleW = destSize.width / sourceSize.width; - CGFloat scaleH = destSize.height / sourceSize.height; - - return CGSizeMake(scaleW, scaleH); -} - -CGFloat AspectScaleFill(CGSize sourceSize, CGRect destRect) -{ - CGSize destSize = destRect.size; - CGFloat scaleW = destSize.width / sourceSize.width; - CGFloat scaleH = destSize.height / sourceSize.height; - return fmax(scaleW, scaleH); -} - -CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect) -{ - CGSize destSize = destRect.size; - CGFloat scaleW = destSize.width / sourceSize.width; - CGFloat scaleH = destSize.height / sourceSize.height; - return fmin(scaleW, scaleH); -} - -CGRect RectByFittingRect(CGRect sourceRect, CGRect destinationRect) -{ - CGFloat aspect = AspectScaleFit(sourceRect.size, destinationRect); - CGSize targetSize = SizeScaleByFactor(sourceRect.size, aspect); - return RectAroundCenter(RectGetCenter(destinationRect), targetSize); -} - -CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect) -{ - CGFloat aspect = AspectScaleFill(sourceRect.size, destinationRect); - CGSize targetSize = SizeScaleByFactor(sourceRect.size, aspect); - return RectAroundCenter(RectGetCenter(destinationRect), targetSize); -} - -CGRect RectInsetByPercent(CGRect rect, CGFloat percent) -{ - CGFloat wInset = rect.size.width * (percent / 2.0f); - CGFloat hInset = rect.size.height * (percent / 2.0f); - return CGRectInset(rect, wInset, hInset); -} - -#pragma mark - Transforms - -// Extract the x scale from transform -CGFloat TransformGetXScale(CGAffineTransform t) -{ - return sqrt(t.a * t.a + t.c * t.c); -} - -// Extract the y scale from transform -CGFloat TransformGetYScale(CGAffineTransform t) -{ - return sqrt(t.b * t.b + t.d * t.d); -} - -// Extract the rotation in radians -CGFloat TransformGetRotation(CGAffineTransform t) -{ - return atan2f(t.b, t.a); -} diff --git a/ios/QuartzBookPack/Image/ImageUtils.h b/ios/QuartzBookPack/Image/ImageUtils.h deleted file mode 100644 index f70ab1eb2..000000000 --- a/ios/QuartzBookPack/Image/ImageUtils.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - Gathered for book examples - - */ - -#import -#import - -// 4 bytes per ARGB pixel, 8 bits per byte -#define ARGB_COUNT 4 -#define BITS_PER_COMPONENT 8 - -UIEdgeInsets BuildInsets(CGRect alignmentRect, CGRect imageBounds); - -UIImage *BuildSwatchWithColor(UIColor *color, CGFloat side); -UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting); -UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect); -UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect); - -UIImage *GrayscaleVersionOfImage(UIImage *sourceImage); -UIImage *InvertImage(UIImage *sourceImage); - -NSData *BytesFromRGBImage(UIImage *sourceImage); -UIImage *ImageFromRGBBytes(NSData *data, CGSize targetSize); - -void FlipContextVertically(CGSize size); -void FlipContextHorizontally(CGSize size); -void FlipImageContextVertically(); -void FlipImageContextHorizontally(); -void RotateContext(CGSize size, CGFloat theta); -void MoveContextByVector(CGPoint vector); - -UIImage *ImageMirroredVertically(UIImage *image); - -void DrawPDFPageInRect(CGPDFPageRef pageRef, CGRect destinationRect); - -UIImage *GaussianBlurImage(UIImage *image, CGFloat radius); diff --git a/ios/QuartzBookPack/Image/ImageUtils.m b/ios/QuartzBookPack/Image/ImageUtils.m deleted file mode 100644 index d108fd7db..000000000 --- a/ios/QuartzBookPack/Image/ImageUtils.m +++ /dev/null @@ -1,372 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "ImageUtils.h" -#import "Utility.h" -#import "BaseGeometry.h" - -// Chapter 3-8 -// Establish insets for image alignment -UIEdgeInsets BuildInsets(CGRect alignmentRect, CGRect imageBounds) -{ - // Ensure alignment rect is fully within source - CGRect targetRect = CGRectIntersection(alignmentRect, imageBounds); - - // Calculate insets - UIEdgeInsets insets; - insets.left = CGRectGetMinX(targetRect) - CGRectGetMinX(imageBounds); - insets.right = CGRectGetMaxX(imageBounds) - CGRectGetMaxX(targetRect); - insets.top = CGRectGetMinY(targetRect) - CGRectGetMinY(imageBounds); - insets.bottom = CGRectGetMaxY(imageBounds) - CGRectGetMaxY(targetRect); - - return insets; -} - - -// Chapter 3 - 1 -UIImage *BuildSwatchWithColor(UIColor *color, CGFloat side) -{ - // Create image context - UIGraphicsBeginImageContextWithOptions( - CGSizeMake(side, side), YES, - 0.0); - - // Perform drawing - [color setFill]; - UIRectFill(CGRectMake(0, 0, side, side)); - - // Retrieve image - UIImage *image = - UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - -// Chapter 3 - 2 -UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting) -{ - CGRect targetRect = SizeMakeRect(targetSize); - UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0); - - CGRect naturalRect = (CGRect){.size = sourceImage.size}; - CGRect destinationRect = useFitting ? RectByFittingRect(naturalRect, targetRect) : RectByFillingRect(naturalRect, targetRect); - [sourceImage drawInRect:destinationRect]; - - UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return thumbnail; -} - -// Chapter 3 - 3 -UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect) -{ - // Extract image - CGImageRef imageRef = CGImageCreateWithImageInRect(sourceImage.CGImage, subRect); - if (imageRef != NULL) - { - UIImage *output = [UIImage imageWithCGImage:imageRef]; - CGImageRelease(imageRef); - return output; - } - - NSLog(@"Error: Unable to extract subimage"); - return nil; -} - -// This is a little less flaky when moving to and from Retina images -UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect) -{ - UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1); - CGRect destRect = CGRectMake(-rect.origin.x, -rect.origin.y, - sourceImage.size.width, sourceImage.size.height); - [sourceImage drawInRect:destRect]; - UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return newImage; -} - -// Chapter 3 - 4 -UIImage *GrayscaleVersionOfImage(UIImage *sourceImage) -{ - // Establish grayscale color space - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); - if (colorSpace == NULL) - { - NSLog(@"Error: Could not establish grayscale color space"); - return nil; - } - - // Extents are integers - int width = sourceImage.size.width; - int height = sourceImage.size.height; - - // Build context: one byte per pixel, no alpha - CGContextRef context = CGBitmapContextCreate(NULL, width, height, BITS_PER_COMPONENT, width, colorSpace, (CGBitmapInfo)kCGImageAlphaNone); - CGColorSpaceRelease(colorSpace); - if (context == NULL) - { - NSLog(@"Error: Could not build grayscale bitmap context"); - return nil; - } - - // Replicate image using new color space - CGRect rect = SizeMakeRect(sourceImage.size); - CGContextDrawImage(context, rect, sourceImage.CGImage); - CGImageRef imageRef = CGBitmapContextCreateImage(context); - CGContextRelease(context); - - // Return the grayscale image - UIImage *output = [UIImage imageWithCGImage:imageRef]; - CFRelease(imageRef); - return output; -} - -// Just for fun. Return image with colors flipped -UIImage *InvertImage(UIImage *sourceImage) -{ - UIGraphicsBeginImageContextWithOptions(sourceImage.size, NO, 0.0); - CGContextRef context = UIGraphicsGetCurrentContext(); - [sourceImage drawInRect:SizeMakeRect(sourceImage.size)]; - CGContextSetBlendMode(context, kCGBlendModeDifference); - CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor); - CGContextFillRect(context, SizeMakeRect(sourceImage.size)); - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return result; -} - -// Chapter 3-6 -// Extract bytes -NSData *BytesFromRGBImage(UIImage *sourceImage) -{ - if (!sourceImage) return nil; - - // Establish color space - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - if (colorSpace == NULL) - { - NSLog(@"Error creating RGB color space"); - return nil; - } - - // Establish context - int width = sourceImage.size.width; - int height = sourceImage.size.height; - CGContextRef context = CGBitmapContextCreate(NULL, width, height, BITS_PER_COMPONENT, width * ARGB_COUNT, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst); - CGColorSpaceRelease(colorSpace ); - if (context == NULL) - { - NSLog(@"Error creating context"); - return nil; - } - - // Draw source into context bytes - CGRect rect = (CGRect){.size = sourceImage.size}; - CGContextDrawImage(context, rect, sourceImage.CGImage); - - // Create NSData from bytes - NSData *data = [NSData dataWithBytes:CGBitmapContextGetData(context) length:(width * height * 4)]; - CGContextRelease(context); - - return data; -} - -// Chapter 3-7 -// Create image from bytes -UIImage *ImageFromRGBBytes(NSData *data, CGSize targetSize) -{ - // Check data - int width = targetSize.width; - int height = targetSize.height; - if (data.length < (width * height * 4)) - { - NSLog(@"Error: Not enough RGB data provided. Got %d bytes. Expected %d bytes", data.length, width * height * 4); - return nil; - } - - // Create a color space - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - if (colorSpace == NULL) - { - NSLog(@"Error creating RGB colorspace"); - return nil; - } - - // Create the bitmap context - Byte *bytes = (Byte *) data.bytes; - CGContextRef context = CGBitmapContextCreate(bytes, width, height, BITS_PER_COMPONENT, width * ARGB_COUNT, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst); - CGColorSpaceRelease(colorSpace ); - if (context == NULL) - { - NSLog(@"Error. Could not create context"); - return nil; - } - - // Convert to image - CGImageRef imageRef = CGBitmapContextCreateImage(context); - UIImage *image = [UIImage imageWithCGImage:imageRef]; - - // Clean up - CGContextRelease(context); - CFRelease(imageRef); - - return image; -} - -#pragma mark - Context - -// From Chapter 1 -void FlipContextVertically(CGSize size) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) - { - NSLog(@"Error: No context to flip"); - return; - } - - CGAffineTransform transform = CGAffineTransformIdentity; - transform = CGAffineTransformScale(transform, 1.0f, -1.0f); - transform = CGAffineTransformTranslate(transform, 0.0f, -size.height); - CGContextConcatCTM(context, transform); -} - -void FlipImageContextVertically() -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) - { - NSLog(@"Error: No context to flip"); - return; - } - - // I don't like this approach - // CGFloat scale = [UIScreen mainScreen].scale; - // CGSize size = CGSizeMake(CGBitmapContextGetWidth(context) / scale, CGBitmapContextGetHeight(context) / scale); - // FlipContextVertically(size); - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - FlipContextVertically(image.size); -} - -void FlipContextHorizontally(CGSize size) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - CGAffineTransform transform = CGAffineTransformIdentity; - transform = CGAffineTransformScale(transform, -1.0f, 1.0f); - transform = CGAffineTransformTranslate(transform, -size.width, 0.0); - CGContextConcatCTM(context, transform); -} - -void FlipImageContextHorizontally() -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) - { - NSLog(@"Error: No context to flip"); - return; - } - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - FlipContextHorizontally(image.size); -} - -void RotateContext(CGSize size, CGFloat theta) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(context, size.width / 2.0f, size.height / 2.0f); - CGContextRotateCTM(context, theta); - CGContextTranslateCTM(context, -size.width / 2.0f, -size.height / 2.0f); -} - -void MoveContextByVector(CGPoint vector) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(context, vector.x, vector.y); -} - -UIImage *ImageMirroredVertically(UIImage *source) -{ - UIGraphicsBeginImageContextWithOptions(source.size, NO, 0.0); - FlipContextVertically(source.size); - [source drawInRect:SizeMakeRect(source.size)]; - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - - -#pragma mark - PDF Util -// Chapter 3-12 -void DrawPDFPageInRect(CGPDFPageRef pageRef, CGRect destinationRect) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) - { - NSLog(@"Error: No context to draw to"); - return; - } - - CGContextSaveGState(context); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - - // Flip the context to Quartz space - CGAffineTransform transform = CGAffineTransformIdentity; - transform = CGAffineTransformScale(transform, 1.0f, -1.0f); - transform = CGAffineTransformTranslate(transform, 0.0f, -image.size.height); - CGContextConcatCTM(context, transform); - - // Flip the rect, which remains in UIKit space - CGRect d = CGRectApplyAffineTransform(destinationRect, transform); - - // Calculate a rectangle to draw to - CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox); - CGFloat drawingAspect = AspectScaleFit(pageRect.size, d); - CGRect drawingRect = RectByFittingRect(pageRect, d); - - // Draw the page outline (optional) - UIRectFrame(drawingRect); - - // Adjust the context - CGContextTranslateCTM(context, drawingRect.origin.x, drawingRect.origin.y); - CGContextScaleCTM(context, drawingAspect, drawingAspect); - - // Draw the page - CGContextDrawPDFPage(context, pageRef); - CGContextRestoreGState(context); -} - -#pragma mark - Masking, Blurring -// Listing 7-3 -UIImage *GaussianBlurImage(UIImage *image, CGFloat radius) -{ - if (!image) COMPLAIN_AND_BAIL_NIL(@"Mask cannot be nil", nil); - - CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; - [blurFilter setValue: [CIImage imageWithCGImage:image.CGImage] - forKey: @"inputImage"]; - [blurFilter setValue:@(radius) forKey:@"inputRadius"]; - - CIFilter *crop = [CIFilter filterWithName: @"CICrop"]; - [crop setDefaults]; - [crop setValue:blurFilter.outputImage forKey:@"inputImage"]; - - CGFloat scale = [[UIScreen mainScreen] scale]; - CGFloat w = image.size.width * scale; - CGFloat h = image.size.height * scale; - CIVector *v = [CIVector vectorWithX:0 Y:0 Z:w W:h]; - [crop setValue:v forKey:@"inputRectangle"]; - - CGImageRef cgImageRef = [[CIContext contextWithOptions:nil] createCGImage:crop.outputImage fromRect:crop.outputImage.extent]; - - UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0); - FlipContextVertically(image.size); - CGContextDrawImage(UIGraphicsGetCurrentContext(), SizeMakeRect(image.size), cgImageRef); - UIImage *blurred = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return blurred; -} \ No newline at end of file diff --git a/ios/QuartzBookPack/TextDrawing/Drawing-Text.h b/ios/QuartzBookPack/TextDrawing/Drawing-Text.h deleted file mode 100644 index 00b4fae92..000000000 --- a/ios/QuartzBookPack/TextDrawing/Drawing-Text.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import -@import Foundation; -@import CoreText; - -#import "Drawing-Text.h" - -// Sizing -NSArray *WidthArrayForStringWithFont(NSString *string, UIFont *font); - -// Drawing -void DrawStringInRect(NSString *string, CGRect rect, UIFont *font, NSTextAlignment alignment, UIColor *color); -void DrawWrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color); -void DrawUnwrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color); -void DrawStringCenteredInRect(NSString *string, UIFont *font, CGRect rect); - -UIFont *FontForWrappedString(NSString *string, NSString *fontFace, CGRect rect, CGFloat tolerance); diff --git a/ios/QuartzBookPack/TextDrawing/Drawing-Text.m b/ios/QuartzBookPack/TextDrawing/Drawing-Text.m deleted file mode 100644 index 46599674c..000000000 --- a/ios/QuartzBookPack/TextDrawing/Drawing-Text.m +++ /dev/null @@ -1,175 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import "Drawing-Text.h" -#import "Utility.h" - -#pragma mark - Drawing - -void DrawStringInRect(NSString *string, CGRect rect, UIFont *font, NSTextAlignment alignment, UIColor *color) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) return; - - NSRange range = NSMakeRange(0, string.length); - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; - - NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; - NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - style.alignment = alignment; - style.lineBreakMode = NSLineBreakByWordWrapping; - attributes[NSFontAttributeName] = font; - attributes[NSForegroundColorAttributeName] = color; - attributes[NSParagraphStyleAttributeName] = style; - [attributedString addAttributes:attributes range:range]; - - CGRect destRect = [string boundingRectWithSize:CGSizeMake(rect.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; - CGRect outputRect = RectCenteredInRect(destRect, rect); - [attributedString drawInRect:outputRect]; -} - -#pragma mark - Unwrapped -UIFont *FontForUnwrappedString(NSString *string, NSString *fontFace, CGRect rect) -{ - CGFloat fontSize = 1; - UIFont *font = [UIFont fontWithName:fontFace size:fontSize]; - CGSize destSize = [string sizeWithAttributes:@{NSFontAttributeName:font}]; - - while ((destSize.width < rect.size.width) && (destSize.height < rect.size.height)) - { - fontSize++; - UIFont *proposedFont = [UIFont fontWithName:fontFace size:fontSize]; - destSize = [string sizeWithAttributes:@{NSFontAttributeName:proposedFont}]; - if ((destSize.height > rect.size.height) || (destSize.width > rect.size.width)) - return font; - - font = proposedFont; - } - - return font; -} - -void DrawUnwrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color) -{ - UIFont *font = FontForUnwrappedString(string, fontFace, rect); - DrawStringInRect(string, rect, font, alignment, color); -} - -void DrawStringCenteredInRect(NSString *string, UIFont *font, CGRect rect) -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - // Calculate string size - CGSize stringSize = [string sizeWithAttributes:@{NSFontAttributeName:font}]; - - // Find the target rectangle - CGRect target = RectAroundCenter(RectGetCenter(rect), stringSize); - - // Draw the string - [string drawInRect:target withAttributes:@{NSFontAttributeName:font}]; -} - - -#pragma mark - Wrapping - -UIFont *FontForWrappedString(NSString *string, NSString *fontFace, CGRect rect, CGFloat tolerance) -{ - if (rect.size.height < 1.0f) return nil; - - CGFloat adjustedWidth = tolerance * rect.size.width; - CGSize measureSize = CGSizeMake(adjustedWidth, CGFLOAT_MAX); - - // Initialize the proposed font - CGFloat fontSize = 1; - UIFont *proposedFont = [UIFont fontWithName:fontFace size:fontSize]; - - NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; - - NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; - attributes[NSParagraphStyleAttributeName] = paragraphStyle; - attributes[NSFontAttributeName] = proposedFont; - - // Measure the target - CGSize targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; - - // Double until the size is exceeded - while (targetSize.height <= rect.size.height) - { - // Establish a new proposed font - fontSize *= 2; - proposedFont = [UIFont fontWithName:fontFace size:fontSize]; - - // Measure the target - attributes[NSFontAttributeName] = proposedFont; - targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; - - // Break when the calculated height is too much - if (targetSize.height > rect.size.height) - break; - } - - // Search between the previous and current font sizes - CGFloat minFontSize = fontSize / 2; - CGFloat maxFontSize = fontSize; - while (1) - { - // Get the midpoint between the two - CGFloat midPoint = (minFontSize + (maxFontSize - minFontSize) / 2); - proposedFont = [UIFont fontWithName:fontFace size:midPoint]; - attributes[NSFontAttributeName] = proposedFont; - targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; - - // Look up one font size - UIFont *nextFont = [UIFont fontWithName:fontFace size:midPoint + 1]; - attributes[NSFontAttributeName] = nextFont; - CGSize nextTargetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;; - - // Test both fonts - CGFloat tooBig = targetSize.height > rect.size.height; - CGFloat nextIsTooBig = nextTargetSize.height > rect.size.height; - - // If the current is sized right but the next is too big, win - if (!tooBig && nextIsTooBig) - return [UIFont fontWithName:fontFace size:midPoint]; - - // Adjust the search space - if (tooBig) - maxFontSize = midPoint; - else - minFontSize = midPoint; - } - - // Should never get here - return [UIFont fontWithName:fontFace size:fontSize / 2]; -} - -CGSize AttributedStringSize(NSAttributedString *string, CGSize constrainedSize) -{ - return [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; -} - -void DrawWrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color) -{ - - UIFont *font = FontForWrappedString(string, fontFace, rect, 0.95); - - NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString:string]; - NSRange r = NSMakeRange(0, string.length); - [s addAttribute:NSFontAttributeName value:font range:r]; - [s addAttribute:NSForegroundColorAttributeName value:color range:r]; - NSMutableParagraphStyle *p = [NSParagraphStyle defaultParagraphStyle].mutableCopy; - p.hyphenationFactor = 0.25f; - p.alignment = alignment; - [s addAttribute:NSParagraphStyleAttributeName value:p range:r]; - - CGRect stringBounds = SizeMakeRect(AttributedStringSize(s, rect.size)); - stringBounds.size.width = fminf(rect.size.width, stringBounds.size.width); - CGRect dest = RectCenteredInRect(stringBounds, rect); - dest.size.height = CGFLOAT_MAX; - [s drawInRect:dest]; -} \ No newline at end of file diff --git a/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h b/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h deleted file mode 100644 index 6fa0befdb..000000000 --- a/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h +++ /dev/null @@ -1,13 +0,0 @@ -/* - Erica Sadun, http://ericasadun.com - iPhone Developer's Cookbook, 6.x Edition - BSD License, Use at your own risk - */ - -#import -#import - -#import "UIBezierPath+Elements.h" -@interface UIBezierPath (TextUtilities) -- (void) drawAttributedString: (NSAttributedString *) string; -@end diff --git a/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m b/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m deleted file mode 100644 index 9d893e664..000000000 --- a/ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m +++ /dev/null @@ -1,56 +0,0 @@ -/* - Erica Sadun, http://ericasadun.com - iPhone Developer's Cookbook, 6.x Edition - BSD License, Use at your own risk - */ - -#import "UIBezierPath+Text.h" -#import "Utility.h" - -@implementation UIBezierPath (TextUtilities) -- (void) drawAttributedString: (NSAttributedString *) string -{ - if (!string) return; - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil); - - if (self.elements.count < 2) return; - - // Keep a running tab of how far the glyphs have traveled to - // be able to calculate the percent along the point path - float glyphDistance = 0.0f; - float lineLength = self.pathLength; - - for (int loc = 0; loc < string.length; loc++) - { - // Retrieve item - NSRange range = NSMakeRange(loc, 1); - NSAttributedString *item = [string attributedSubstringFromRange:range]; - - // Start halfway through each glyph - CGRect bounding = [item boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 context:nil]; - glyphDistance += bounding.size.width / 2; - - // Find point - CGPoint slope; - CGFloat percentConsumed = glyphDistance / lineLength; - CGPoint targetPoint = [self pointAtPercent:percentConsumed withSlope:&slope]; - - // Accommodate the forward progress - glyphDistance += bounding.size.width / 2; - if (percentConsumed >= 1.0f) break; - - // Calculate the rotation - float angle = atan(slope.y / slope.x); // + M_PI; - if (slope.x < 0) angle += M_PI; // going left, update the angle - - // Draw the glyph - PushDraw(^{ - CGContextTranslateCTM(context, targetPoint.x, targetPoint.y); - CGContextRotateCTM(context, angle); - CGContextTranslateCTM(context, -bounding.size.width / 2, -item.size.height / 2); - [item drawAtPoint:CGPointZero]; - }); - } -} -@end diff --git a/ios/QuartzBookPack/Utility/Utility.h b/ios/QuartzBookPack/Utility/Utility.h deleted file mode 100644 index 3ad23f585..000000000 --- a/ios/QuartzBookPack/Utility/Utility.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - - Erica Sadun, http://ericasadun.com - - */ - -#import -#import - -#import "BaseGeometry.h" -#import "Drawing-Block.h" -#import "Drawing-Util.h" -#import "Drawing-Gradient.h" -#import "Bezier.h" -#import "ImageUtils.h" -#import "UIBezierPath+Text.h" -#import "Drawing-Text.h" - -#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] initWithTitle:TITLE style:UIBarButtonItemStylePlain target:self action:SELECTOR] -#define RGBCOLOR(_R_, _G_, _B_) [UIColor colorWithRed:(CGFloat)(_R_)/255.0f green: (CGFloat)(_G_)/255.0f blue: (CGFloat)(_B_)/255.0f alpha: 1.0f] - -#define OLIVE RGBCOLOR(125, 162, 63) -#define LIGHTPURPLE RGBCOLOR(99, 62, 162) -#define DARKGREEN RGBCOLOR(40, 55, 32) - -// Bail with complaint -#define COMPLAIN_AND_BAIL(_COMPLAINT_, _ARG_) {NSLog(_COMPLAINT_, _ARG_); return;} -#define COMPLAIN_AND_BAIL_NIL(_COMPLAINT_, _ARG_) {NSLog(_COMPLAINT_, _ARG_); return nil;} - -#define SEED_RANDOM {static BOOL seeded = NO; if (!seeded) {seeded = YES; srandom((unsigned int) time(0));}} -#define RANDOM(_X_) (NSInteger)(random() % _X_) -#define RANDOM_01 ((double) random() / (double) LONG_MAX) -#define RANDOM_BOOL (BOOL)((NSInteger)random() % 2) -#define RANDOM_PT(_RECT_) CGPointMake(_RECT_.origin.x + RANDOM_01 * _RECT_.size.width, _RECT_.origin.y + RANDOM_01 * _RECT_.size.height) - -UIBezierPath *BuildBunnyPath(); -UIBezierPath *BuildMoofPath(); -UIBezierPath *BuildStarPath(); - -UIColor *NoiseColor(); - -@interface NSString (Utility) -+ (NSString *) lorem: (NSUInteger) numberOfParagraphs; -+ (NSString *) loremWords: (NSUInteger) numberOfWords; -@end - -#define DEBUG_IMAGE(_IMAGE_, _NAME_) [UIImagePNGRepresentation(_IMAGE_) writeToFile:[NSString stringWithFormat:@"/Users/ericasadun/Desktop/%@.png", _NAME_] atomically:YES] - diff --git a/ios/QuartzBookPack/Utility/Utility.m b/ios/QuartzBookPack/Utility/Utility.m deleted file mode 100644 index dacb2cc18..000000000 --- a/ios/QuartzBookPack/Utility/Utility.m +++ /dev/null @@ -1,240 +0,0 @@ -#import "Utility.h" - -typedef BOOL (^TestingBlock)(id object); -@interface NSArray (Utility) -@end - -@implementation NSArray (Utility) -- (NSArray *) collect: (TestingBlock) aBlock -{ - NSMutableArray *resultArray = [NSMutableArray array]; - for (id object in self) - { - BOOL result = aBlock(object); - if (result) - [resultArray addObject:object]; - } - return resultArray; -} -@end - -@implementation NSString (Utility) -+ (NSString *) ipsum:(NSUInteger) numberOfParagraphs -{ - NSString *urlString = [NSString stringWithFormat:@"http://loripsum.net/api/%0d/short/prude/plaintext", numberOfParagraphs]; - - NSError *error; - NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString] encoding:NSUTF8StringEncoding error:&error]; - if (!string) - { - NSLog(@"Error: %@", error.localizedDescription); - return nil; - } - return string; -} - -+ (NSString *) lorem:(NSUInteger) numberOfParagraphs -{ - return [self ipsum:numberOfParagraphs]; -} - -- (NSArray *) words -{ - NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - TestingBlock block = ^BOOL(id word){return [(NSString *)word length] > 0;}; - return [words collect:block]; -} - -- (NSString *) wordRange: (NSRange) range -{ - NSArray *componentWords = self.words; - NSInteger start = range.location; - NSInteger end = range.location + range.length; - if ((start >= componentWords.count) || (end >= componentWords.count)) - return nil; - - NSArray *subArray = [componentWords subarrayWithRange:range]; - return [subArray componentsJoinedByString:@" "]; -} - -+ (NSString *) loremWords: (NSUInteger) numberOfWords -{ - // meant for up to 10 words - NSUInteger nWords = MIN(numberOfWords, 10); - NSRange r = NSMakeRange(0, nWords); - return [[NSString lorem:3] wordRange:r]; -} -@end - -UIBezierPath *BuildBunnyPath() -{ - UIBezierPath *shapePath = [UIBezierPath bezierPath]; - [shapePath moveToPoint: CGPointMake(392.05, 150.53)]; - [shapePath addCurveToPoint: CGPointMake(343.11, 129.56) controlPoint1: CGPointMake(379.34, 133.37) controlPoint2: CGPointMake(359, 133.37)]; - [shapePath addCurveToPoint: CGPointMake(305.61, 71.72) controlPoint1: CGPointMake(341.2, 119.39) controlPoint2: CGPointMake(316.41, 71.08)]; - [shapePath addCurveToPoint: CGPointMake(304.34, 100.96) controlPoint1: CGPointMake(294.8, 72.35) controlPoint2: CGPointMake(302.43, 79.35)]; - [shapePath addCurveToPoint: CGPointMake(283.36, 84.43) controlPoint1: CGPointMake(299.25, 95.24) controlPoint2: CGPointMake(287.81, 73.63)]; - [shapePath addCurveToPoint: CGPointMake(301.79, 154.99) controlPoint1: CGPointMake(272.42, 111.01) controlPoint2: CGPointMake(302.43, 148.63)]; - [shapePath addCurveToPoint: CGPointMake(287.17, 191.85) controlPoint1: CGPointMake(301.16, 161.34) controlPoint2: CGPointMake(299, 186.78)]; - [shapePath addCurveToPoint: CGPointMake(194.38, 221.09) controlPoint1: CGPointMake(282.72, 193.76) controlPoint2: CGPointMake(240.78, 195.66)]; - [shapePath addCurveToPoint: CGPointMake(128.27, 320.25) controlPoint1: CGPointMake(147.97, 246.51) controlPoint2: CGPointMake(138.44, 282.11)]; - [shapePath addCurveToPoint: CGPointMake(124.75, 348.92) controlPoint1: CGPointMake(125.53, 330.51) controlPoint2: CGPointMake(124.54, 340.12)]; - [shapePath addCurveToPoint: CGPointMake(118.34, 358.13) controlPoint1: CGPointMake(122.92, 350.31) controlPoint2: CGPointMake(120.74, 353.02)]; - [shapePath addCurveToPoint: CGPointMake(136.06, 388.68) controlPoint1: CGPointMake(112.42, 370.73) controlPoint2: CGPointMake(123.06, 383.91)]; - [shapePath addCurveToPoint: CGPointMake(144.8, 399.06) controlPoint1: CGPointMake(138.8, 392.96) controlPoint2: CGPointMake(141.79, 396.49)]; - [shapePath addCurveToPoint: CGPointMake(210.9, 408.6) controlPoint1: CGPointMake(158.14, 410.5) controlPoint2: CGPointMake(205.18, 406.69)]; - [shapePath addCurveToPoint: CGPointMake(240.78, 417.5) controlPoint1: CGPointMake(216.62, 410.5) controlPoint2: CGPointMake(234.41, 417.5)]; - [shapePath addCurveToPoint: CGPointMake(267.47, 411.78) controlPoint1: CGPointMake(247.13, 417.5) controlPoint2: CGPointMake(267.47, 419.4)]; - [shapePath addCurveToPoint: CGPointMake(250.3, 385.71) controlPoint1: CGPointMake(267.47, 394.61) controlPoint2: CGPointMake(250.3, 385.71)]; - [shapePath addCurveToPoint: CGPointMake(274.46, 371.73) controlPoint1: CGPointMake(250.3, 385.71) controlPoint2: CGPointMake(260.48, 379.99)]; - [shapePath addCurveToPoint: CGPointMake(302.43, 350.12) controlPoint1: CGPointMake(288.45, 363.47) controlPoint2: CGPointMake(297.34, 350.76)]; - [shapePath addCurveToPoint: CGPointMake(318.32, 389.53) controlPoint1: CGPointMake(312.22, 348.89) controlPoint2: CGPointMake(311.33, 381.9)]; - [shapePath addCurveToPoint: CGPointMake(341.84, 421.94) controlPoint1: CGPointMake(325.32, 397.15) controlPoint2: CGPointMake(332.15, 418.51)]; - [shapePath addCurveToPoint: CGPointMake(375.53, 413.68) controlPoint1: CGPointMake(349.08, 424.51) controlPoint2: CGPointMake(373.62, 421.31)]; - [shapePath addCurveToPoint: CGPointMake(367.26, 398.43) controlPoint1: CGPointMake(377.43, 406.06) controlPoint2: CGPointMake(372.35, 405.42)]; - [shapePath addCurveToPoint: CGPointMake(361.54, 352.66) controlPoint1: CGPointMake(362.18, 391.43) controlPoint2: CGPointMake(356.46, 363.47)]; - [shapePath addCurveToPoint: CGPointMake(378.07, 277.66) controlPoint1: CGPointMake(366.63, 341.86) controlPoint2: CGPointMake(376.8, 296.73)]; - [shapePath addCurveToPoint: CGPointMake(388.87, 220.45) controlPoint1: CGPointMake(379.34, 258.59) controlPoint2: CGPointMake(378.7, 245.88)]; - [shapePath addCurveToPoint: CGPointMake(411.12, 189.31) controlPoint1: CGPointMake(405.4, 214.1) controlPoint2: CGPointMake(410.48, 197.57)]; - [shapePath addCurveToPoint: CGPointMake(392.05, 150.53) controlPoint1: CGPointMake(411.76, 181.04) controlPoint2: CGPointMake(404.76, 167.7)]; - [shapePath closePath]; - return shapePath; -} - -// http://pastie.org/private/7i0lgpfqtlkihbjyn0ba -// http://imgur.com/Po3uoIy -UIBezierPath *BuildMoofPath() -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - [path moveToPoint: CGPointMake(107, 66)]; - [path addCurveToPoint: CGPointMake(119, 69) controlPoint1: CGPointMake(113.17, 65.65) controlPoint2: CGPointMake(116.61, 65.38)]; - [path addCurveToPoint: CGPointMake(118, 100) controlPoint1: CGPointMake(118.67, 79.33) controlPoint2: CGPointMake(118.33, 89.67)]; - [path addCurveToPoint: CGPointMake(140, 114) controlPoint1: CGPointMake(125.33, 104.67) controlPoint2: CGPointMake(132.67, 109.33)]; - [path addCurveToPoint: CGPointMake(240, 105) controlPoint1: CGPointMake(169.22, 124.94) controlPoint2: CGPointMake(219.91, 117.23)]; - [path addCurveToPoint: CGPointMake(261, 76) controlPoint1: CGPointMake(252.29, 97.52) controlPoint2: CGPointMake(251.99, 86.39)]; - [path addCurveToPoint: CGPointMake(267, 78) controlPoint1: CGPointMake(263, 76.67) controlPoint2: CGPointMake(265, 77.33)]; - [path addCurveToPoint: CGPointMake(255, 145) controlPoint1: CGPointMake(276.45, 112.7) controlPoint2: CGPointMake(260.16, 119.11)]; - [path addCurveToPoint: CGPointMake(262, 188) controlPoint1: CGPointMake(257.33, 159.33) controlPoint2: CGPointMake(259.67, 173.67)]; - [path addCurveToPoint: CGPointMake(208, 232) controlPoint1: CGPointMake(268.07, 224.31) controlPoint2: CGPointMake(240.23, 239.3)]; - [path addCurveToPoint: CGPointMake(206, 225) controlPoint1: CGPointMake(207.33, 229.67) controlPoint2: CGPointMake(206.67, 227.33)]; - [path addCurveToPoint: CGPointMake(220, 192) controlPoint1: CGPointMake(217.21, 215.24) controlPoint2: CGPointMake(226.39, 211.63)]; - [path addCurveToPoint: CGPointMake(127, 186) controlPoint1: CGPointMake(197.32, 181.14) controlPoint2: CGPointMake(155.61, 179.26)]; - [path addCurveToPoint: CGPointMake(78, 230) controlPoint1: CGPointMake(126.93, 214.62) controlPoint2: CGPointMake(110.79, 250.16)]; - [path addCurveToPoint: CGPointMake(86, 203) controlPoint1: CGPointMake(76.79, 220.05) controlPoint2: CGPointMake(83.57, 212.84)]; - [path addCurveToPoint: CGPointMake(85, 132) controlPoint1: CGPointMake(90.93, 183.08) controlPoint2: CGPointMake(90.79, 150.81)]; - [path addCurveToPoint: CGPointMake(28, 121) controlPoint1: CGPointMake(60.95, 132.68) controlPoint2: CGPointMake(39.36, 132.88)]; - [path addCurveToPoint: CGPointMake(57, 91) controlPoint1: CGPointMake(27.34, 95.81) controlPoint2: CGPointMake(48.07, 104.89)]; - [path addCurveToPoint: CGPointMake(60, 73) controlPoint1: CGPointMake(58, 85) controlPoint2: CGPointMake(59, 79)]; - [path addCurveToPoint: CGPointMake(85, 76) controlPoint1: CGPointMake(66.31, 61.96) controlPoint2: CGPointMake(80.38, 68.36)]; - [path addCurveToPoint: CGPointMake(107, 66) controlPoint1: CGPointMake(93.79, 75.13) controlPoint2: CGPointMake(101.6, 70.61)]; - [path closePath]; - [path moveToPoint: CGPointMake(116, 69)]; - [path addCurveToPoint: CGPointMake(84, 78) controlPoint1: CGPointMake(104.12, 69.8) controlPoint2: CGPointMake(92.62, 83.34)]; - [path addCurveToPoint: CGPointMake(77, 72) controlPoint1: CGPointMake(81.67, 76) controlPoint2: CGPointMake(79.33, 74)]; - [path addCurveToPoint: CGPointMake(67, 72) controlPoint1: CGPointMake(73.67, 72) controlPoint2: CGPointMake(70.33, 72)]; - [path addCurveToPoint: CGPointMake(60, 93) controlPoint1: CGPointMake(60.55, 76.74) controlPoint2: CGPointMake(64.31, 86.1)]; - [path addCurveToPoint: CGPointMake(32, 111) controlPoint1: CGPointMake(54.17, 102.33) controlPoint2: CGPointMake(37.4, 101.45)]; - [path addCurveToPoint: CGPointMake(32, 121) controlPoint1: CGPointMake(32, 114.33) controlPoint2: CGPointMake(32, 117.67)]; - [path addCurveToPoint: CGPointMake(88, 129) controlPoint1: CGPointMake(44.53, 129.24) controlPoint2: CGPointMake(66.88, 129.21)]; - [path addCurveToPoint: CGPointMake(109, 172) controlPoint1: CGPointMake(93.2, 149.04) controlPoint2: CGPointMake(120.58, 148.18)]; - [path addCurveToPoint: CGPointMake(92, 176) controlPoint1: CGPointMake(103.33, 173.33) controlPoint2: CGPointMake(97.67, 174.67)]; - [path addCurveToPoint: CGPointMake(83, 230) controlPoint1: CGPointMake(91.91, 195.18) controlPoint2: CGPointMake(78.84, 222.44)]; - [path addCurveToPoint: CGPointMake(85, 230) controlPoint1: CGPointMake(83.67, 230) controlPoint2: CGPointMake(84.33, 230)]; - [path addCurveToPoint: CGPointMake(127, 183) controlPoint1: CGPointMake(131.62, 242.71) controlPoint2: CGPointMake(110.92, 195.36)]; - [path addCurveToPoint: CGPointMake(223, 190) controlPoint1: CGPointMake(149.67, 169.94) controlPoint2: CGPointMake(207.14, 180.38)]; - [path addCurveToPoint: CGPointMake(209, 226) controlPoint1: CGPointMake(228.64, 206.57) controlPoint2: CGPointMake(224.64, 221.84)]; - [path addCurveToPoint: CGPointMake(210, 229) controlPoint1: CGPointMake(209.33, 227) controlPoint2: CGPointMake(209.67, 228)]; - [path addCurveToPoint: CGPointMake(258, 184) controlPoint1: CGPointMake(240.81, 233.96) controlPoint2: CGPointMake(265.07, 221.63)]; - [path addCurveToPoint: CGPointMake(251, 147) controlPoint1: CGPointMake(255.67, 171.67) controlPoint2: CGPointMake(253.33, 159.33)]; - [path addCurveToPoint: CGPointMake(262, 79) controlPoint1: CGPointMake(253.73, 128.51) controlPoint2: CGPointMake(281.22, 99.39)]; - [path addCurveToPoint: CGPointMake(230, 113) controlPoint1: CGPointMake(260.12, 98.13) controlPoint2: CGPointMake(245.13, 107.06)]; - [path addCurveToPoint: CGPointMake(174, 146) controlPoint1: CGPointMake(229.65, 133.05) controlPoint2: CGPointMake(198.37, 155.24)]; - [path addCurveToPoint: CGPointMake(150, 119) controlPoint1: CGPointMake(166, 137) controlPoint2: CGPointMake(158, 128)]; - [path addCurveToPoint: CGPointMake(101, 96) controlPoint1: CGPointMake(138.77, 111.56) controlPoint2: CGPointMake(109.21, 105.11)]; - [path addCurveToPoint: CGPointMake(116, 69) controlPoint1: CGPointMake(103.48, 82.21) controlPoint2: CGPointMake(113.99, 82.77)]; - [path closePath]; - [path moveToPoint: CGPointMake(76, 93)]; - [path addCurveToPoint: CGPointMake(82, 93) controlPoint1: CGPointMake(78, 93) controlPoint2: CGPointMake(80, 93)]; - [path addCurveToPoint: CGPointMake(82, 98) controlPoint1: CGPointMake(82, 94.67) controlPoint2: CGPointMake(82, 96.33)]; - [path addCurveToPoint: CGPointMake(76, 98) controlPoint1: CGPointMake(80, 98) controlPoint2: CGPointMake(78, 98)]; - [path addCurveToPoint: CGPointMake(76, 93) controlPoint1: CGPointMake(76, 96.33) controlPoint2: CGPointMake(76, 94.67)]; - [path closePath]; - return path; -} - -UIBezierPath *BuildStarPath() -{ - // Create new path, courtesy of PaintCode (paintcodeapp.com) - UIBezierPath* bezierPath = [UIBezierPath bezierPath]; - - // Move to the start of the path - [bezierPath moveToPoint: CGPointMake(883.23, 430.54)]; - - // Add the cubic segments - [bezierPath addCurveToPoint: CGPointMake(749.25, 358.4) - controlPoint1: CGPointMake(873.68, 370.91) - controlPoint2: CGPointMake(809.43, 367.95)]; - [bezierPath addCurveToPoint: CGPointMake(668.1, 353.25) - controlPoint1: CGPointMake(721.92, 354.07) - controlPoint2: CGPointMake(690.4, 362.15)]; - [bezierPath addCurveToPoint: CGPointMake(492.9, 156.15) - controlPoint1: CGPointMake(575.39, 316.25) - controlPoint2: CGPointMake(629.21, 155.47)]; - [bezierPath addCurveToPoint: CGPointMake(461.98, 169.03) - controlPoint1: CGPointMake(482.59, 160.45) - controlPoint2: CGPointMake(472.29, 164.74)]; - [bezierPath addCurveToPoint: CGPointMake(365.36, 345.52) - controlPoint1: CGPointMake(409.88, 207.98) - controlPoint2: CGPointMake(415.22, 305.32)]; - [bezierPath addCurveToPoint: CGPointMake(262.31, 358.4) - controlPoint1: CGPointMake(341.9, 364.44) - controlPoint2: CGPointMake(300.41, 352.37)]; - [bezierPath addCurveToPoint: CGPointMake(133.48, 460.17) - controlPoint1: CGPointMake(200.89, 368.12) - controlPoint2: CGPointMake(118.62, 376.61)]; - [bezierPath addCurveToPoint: CGPointMake(277.77, 622.49) - controlPoint1: CGPointMake(148.46, 544.36) - controlPoint2: CGPointMake(258.55, 560.05)]; - [bezierPath addCurveToPoint: CGPointMake(277.77, 871.12) - controlPoint1: CGPointMake(301.89, 700.9) - controlPoint2: CGPointMake(193.24, 819.76)]; - [bezierPath addCurveToPoint: CGPointMake(513.51, 798.97) - controlPoint1: CGPointMake(382.76, 934.9) - controlPoint2: CGPointMake(435.24, 786.06)]; - [bezierPath addCurveToPoint: CGPointMake(723.49, 878.84) - controlPoint1: CGPointMake(582.42, 810.35) - controlPoint2: CGPointMake(628.93, 907.89)]; - [bezierPath addCurveToPoint: CGPointMake(740.24, 628.93) - controlPoint1: CGPointMake(834.7, 844.69) - controlPoint2: CGPointMake(722.44, 699.2)]; - [bezierPath addCurveToPoint: CGPointMake(883.23, 430.54) - controlPoint1: CGPointMake(756.58, 564.39) - controlPoint2: CGPointMake(899.19, 530.23)]; - - // Close the path. It’s now ready to draw - [bezierPath closePath]; - return bezierPath; -} - -UIColor *NoiseColor() -{ - static UIImage *noise = nil; - if (noise) - return [UIColor colorWithPatternImage:noise]; - - srandom(time(0)); - - CGSize size = CGSizeMake(128, 128); - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - for (int j = 0; j < size.height; j++) - for (int i = 0; i < size.height; i++) - { - UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(i, j, 1, 1)]; - CGFloat level = ((double) random() / (double) LONG_MAX); - [path fill:[UIColor colorWithWhite:level alpha:1]]; - } - noise = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return [UIColor colorWithPatternImage:noise]; -} diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 8157a3682..6fa3e1649 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -53,18 +53,6 @@ 7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260CD1E3499BC00A39833 /* RNSVGViewBox.m */; }; 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; }; 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */; }; - 945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */; }; - 945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */; }; - 945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */; }; - 945A8AE61F4CC037004BBF6B /* BaseGeometry.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */; }; - 945A8AE81F4CC04C004BBF6B /* BezierUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */; }; - 945A8AEA1F4CC050004BBF6B /* BezierFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */; }; - 945A8AEC1F4CC055004BBF6B /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AEB1F4CC055004BBF6B /* BezierElement.m */; }; - 945A8AEE1F4CC07A004BBF6B /* Utility.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AED1F4CC07A004BBF6B /* Utility.m */; }; - 945A8AF01F4CC092004BBF6B /* Drawing-Block.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */; }; - 945A8AF31F4CC0AF004BBF6B /* Drawing-Gradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */; }; - 945A8AF41F4CC0AF004BBF6B /* Drawing-Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */; }; - 945A8AF61F4CC0C1004BBF6B /* ImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */; }; 945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; 945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; 9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */; }; @@ -288,18 +276,6 @@ 7FC260D21E34A12A00A39833 /* RNSVGSymbolManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGSymbolManager.h; sourceTree = ""; }; 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = ""; }; 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */ = {isa = PBXFileReference; lastKnownFileType = folder; path = QuartzBookPack; sourceTree = ""; }; - 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Text.m"; path = "QuartzBookPack/TextDrawing/Drawing-Text.m"; sourceTree = ""; }; - 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Text.m"; path = "QuartzBookPack/TextDrawing/UIBezierPath+Text.m"; sourceTree = ""; }; - 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Elements.m"; path = "QuartzBookPack/Bezier/UIBezierPath+Elements.m"; sourceTree = ""; }; - 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BaseGeometry.m; path = QuartzBookPack/Geometry/BaseGeometry.m; sourceTree = ""; }; - 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierUtils.m; path = QuartzBookPack/Bezier/BezierUtils.m; sourceTree = ""; }; - 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierFunctions.m; path = QuartzBookPack/Bezier/BezierFunctions.m; sourceTree = ""; }; - 945A8AEB1F4CC055004BBF6B /* BezierElement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierElement.m; path = QuartzBookPack/Bezier/BezierElement.m; sourceTree = ""; }; - 945A8AED1F4CC07A004BBF6B /* Utility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Utility.m; path = QuartzBookPack/Utility/Utility.m; sourceTree = ""; }; - 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Block.m"; path = "QuartzBookPack/Drawing/Drawing-Block.m"; sourceTree = ""; }; - 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Gradient.m"; path = "QuartzBookPack/Drawing/Drawing-Gradient.m"; sourceTree = ""; }; - 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Util.m"; path = "QuartzBookPack/Drawing/Drawing-Util.m"; sourceTree = ""; }; - 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ImageUtils.m; path = QuartzBookPack/Image/ImageUtils.m; sourceTree = ""; }; 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlignmentBaseline.m; path = Text/AlignmentBaseline.m; sourceTree = ""; }; 945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AlignmentBaseline.h; path = Text/AlignmentBaseline.h; sourceTree = ""; }; 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PerformanceBezier.xcodeproj; path = PerformanceBezier/PerformanceBezier.xcodeproj; sourceTree = ""; }; @@ -366,18 +342,6 @@ 0CF68AB81AF0540F00FF9E5C = { isa = PBXGroup; children = ( - 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */, - 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */, - 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */, - 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */, - 945A8AED1F4CC07A004BBF6B /* Utility.m */, - 945A8AEB1F4CC055004BBF6B /* BezierElement.m */, - 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */, - 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */, - 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */, - 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */, - 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */, - 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */, 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */, 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */, 1039D29A1CE7212C001E90A8 /* Utils */, @@ -687,18 +651,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 945A8AF01F4CC092004BBF6B /* Drawing-Block.m in Sources */, - 945A8AF31F4CC0AF004BBF6B /* Drawing-Gradient.m in Sources */, - 945A8AF41F4CC0AF004BBF6B /* Drawing-Util.m in Sources */, - 945A8AF61F4CC0C1004BBF6B /* ImageUtils.m in Sources */, - 945A8AEE1F4CC07A004BBF6B /* Utility.m in Sources */, - 945A8AE81F4CC04C004BBF6B /* BezierUtils.m in Sources */, - 945A8AEC1F4CC055004BBF6B /* BezierElement.m in Sources */, - 945A8AEA1F4CC050004BBF6B /* BezierFunctions.m in Sources */, - 945A8AE61F4CC037004BBF6B /* BaseGeometry.m in Sources */, - 945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */, - 945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */, - 945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */, 10BA0D3F1CE74E3100887C2B /* RNSVGTextManager.m in Sources */, 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */, 10BA0D4B1CE74E3D00887C2B /* RNSVGRect.m in Sources */, diff --git a/package.json b/package.json index 760c9d1d6..713606504 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "6.0.0-rc1", + "version": "6.0.0-rc10", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { @@ -19,7 +19,8 @@ "gradient" ], "scripts": { - "lint": "eslint ./" + "lint": "eslint ./", + "postinstall" : "node scripts/install.js" }, "peerDependencies": { "react-native": ">=0.50.0", @@ -28,7 +29,8 @@ }, "dependencies": { "color": "^0.11.1", - "lodash": "^4.16.6" + "lodash": "^4.16.6", + "github-download": "^0.5.0" }, "devDependencies": { "babel-eslint": "^6.1.2", diff --git a/scripts/install.js b/scripts/install.js new file mode 100644 index 000000000..379fa89a7 --- /dev/null +++ b/scripts/install.js @@ -0,0 +1,20 @@ +var path = require('path'); +var ghdownload = require('github-download'); + +function downloadSubModuleFromGithub(user, repo, callback) { + var dist = path.join(process.cwd(), 'ios/' + repo); + + console.log('\r\n Start downloading ' + repo + ' to `' + dist + '`'); + ghdownload({user: user, repo: repo, ref: 'master'}, dist) + .on('end', function() { + console.log('Download ' + repo + ' library success!'); + callback && callback(); + }) + .on('error', function (err) { + console.error('Download ' + repo + ' library from github failed with err:', err); + }); +} + +downloadSubModuleFromGithub('adamwulf', 'PerformanceBezier', function () { + downloadSubModuleFromGithub('magicismight', 'QuartzBookPack'); +}); From 6081b2782278e0632d96d79d659c3f1a6eadc002 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Tue, 7 Nov 2017 19:29:36 +0800 Subject: [PATCH 192/198] Fix fontWeight and fontStyle --- ios/Text/FontData.h | 2 -- ios/Text/FontWeight.m | 2 +- ios/Text/GlyphContext.m | 5 +++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ios/Text/FontData.h b/ios/Text/FontData.h index ec8acc9b7..4658026ba 100644 --- a/ios/Text/FontData.h +++ b/ios/Text/FontData.h @@ -14,10 +14,8 @@ NSString * fontSize_; NSString *fontFamily; enum FontStyle fontStyle; - NSString * fontStyle_; NSDictionary * fontData; enum FontWeight fontWeight; - NSString * fontWeight_; NSString *fontFeatureSettings; enum FontVariantLigatures fontVariantLigatures; enum TextAnchor textAnchor; diff --git a/ios/Text/FontWeight.m b/ios/Text/FontWeight.m index 3a1e6d76d..3bf1d5a54 100644 --- a/ios/Text/FontWeight.m +++ b/ios/Text/FontWeight.m @@ -9,7 +9,7 @@ enum FontWeight FontWeightFromString( NSString* s ) { const NSUInteger l = sizeof(FontWeightStrings) / sizeof(NSString*); for (NSUInteger i = 0; i < l; i++) { - if ([s isEqualToString:FontWeightStrings[i]]) { + if ([[s capitalizedString] isEqualToString:FontWeightStrings[i]]) { return i; } } diff --git a/ios/Text/GlyphContext.m b/ios/Text/GlyphContext.m index 55c16fa5f..ebb3cbda9 100644 --- a/ios/Text/GlyphContext.m +++ b/ios/Text/GlyphContext.m @@ -82,8 +82,9 @@ - (CTFontRef)getGlyphFont { NSString *fontFamily = topFont_->fontFamily; NSNumber * fontSize = [NSNumber numberWithDouble:topFont_->fontSize]; - NSString * fontWeight = topFont_->fontWeight_; - NSString * fontStyle = topFont_->fontStyle_; + + NSString * fontWeight = [FontWeightToString(topFont_->fontWeight) lowercaseString]; + NSString * fontStyle = FontStyleStrings[topFont_->fontStyle]; BOOL fontFamilyFound = NO; NSArray *supportedFontFamilyNames = [UIFont familyNames]; From 177f71c801b6838b000928866f6bff0f30500189 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Tue, 7 Nov 2017 21:03:53 +0800 Subject: [PATCH 193/198] import QuartzBookPack as framework --- ios/RNSVG.xcodeproj/project.pbxproj | 47 +++++++++++++++++++++++++++-- ios/Text/RNSVGTSpan.m | 30 +++++++++--------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 6fa3e1649..a9ac60b6c 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ 7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE991E23476900650F83 /* RNSVGTSpanManager.m */; }; 7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */; }; 7F08CEA11E23479700650F83 /* RNSVGTSpan.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */; }; + 7F4BB50A1FB1E50000663D5F /* QuartzBookPack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4BB5051FB1DEC300663D5F /* QuartzBookPack.framework */; }; 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */; }; 7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260CD1E3499BC00A39833 /* RNSVGViewBox.m */; }; 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; }; @@ -137,6 +138,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 7F4BB5041FB1DEC300663D5F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 7F917B141FB1D5E900A75AA4; + remoteInfo = QuartzBookPack; + }; + 7F4BB5061FB1DEC300663D5F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 7F917B1D1FB1D5E900A75AA4; + remoteInfo = QuartzBookPackTests; + }; 9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; @@ -266,6 +281,7 @@ 7F08CE9E1E23479700650F83 /* RNSVGTSpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTSpan.h; path = Text/RNSVGTSpan.h; sourceTree = ""; }; 7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGTSpan.m; path = Text/RNSVGTSpan.m; sourceTree = ""; }; 7F08CEA31E23481F00650F83 /* RNSVGTextAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextAnchor.h; path = Utils/RNSVGTextAnchor.h; sourceTree = ""; }; + 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = QuartzBookPack.xcodeproj; path = QuartzBookPack/QuartzBookPack.xcodeproj; sourceTree = ""; }; 7F69160D1E3703D800DA6EDC /* RNSVGUnits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGUnits.h; path = Utils/RNSVGUnits.h; sourceTree = ""; }; 7F9CDAF81E1F809C00E0C805 /* RNSVGPathParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGPathParser.h; path = Utils/RNSVGPathParser.h; sourceTree = ""; }; 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGPathParser.m; path = Utils/RNSVGPathParser.m; sourceTree = ""; }; @@ -275,7 +291,6 @@ 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGSymbol.m; path = Elements/RNSVGSymbol.m; sourceTree = ""; }; 7FC260D21E34A12A00A39833 /* RNSVGSymbolManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGSymbolManager.h; sourceTree = ""; }; 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = ""; }; - 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */ = {isa = PBXFileReference; lastKnownFileType = folder; path = QuartzBookPack; sourceTree = ""; }; 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlignmentBaseline.m; path = Text/AlignmentBaseline.m; sourceTree = ""; }; 945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AlignmentBaseline.h; path = Text/AlignmentBaseline.h; sourceTree = ""; }; 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PerformanceBezier.xcodeproj; path = PerformanceBezier/PerformanceBezier.xcodeproj; sourceTree = ""; }; @@ -319,6 +334,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7F4BB50A1FB1E50000663D5F /* QuartzBookPack.framework in Frameworks */, 9494C4E01F473BED00D5BCFD /* Accelerate.framework in Frameworks */, 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */, 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */, @@ -342,7 +358,7 @@ 0CF68AB81AF0540F00FF9E5C = { isa = PBXGroup; children = ( - 945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */, + 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */, 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */, 1039D29A1CE7212C001E90A8 /* Utils */, 1039D2801CE71DCF001E90A8 /* Elements */, @@ -533,6 +549,15 @@ name = Utils; sourceTree = ""; }; + 7F4BB5001FB1DEC300663D5F /* Products */ = { + isa = PBXGroup; + children = ( + 7F4BB5051FB1DEC300663D5F /* QuartzBookPack.framework */, + 7F4BB5071FB1DEC300663D5F /* QuartzBookPackTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 9494C2B31F46139600D5BCFD /* Frameworks */ = { isa = PBXGroup; children = ( @@ -620,6 +645,10 @@ ProductGroup = 9494C4721F4710FE00D5BCFD /* Products */; ProjectRef = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; }, + { + ProductGroup = 7F4BB5001FB1DEC300663D5F /* Products */; + ProjectRef = 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */; + }, ); projectRoot = ""; targets = ( @@ -630,6 +659,20 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 7F4BB5051FB1DEC300663D5F /* QuartzBookPack.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = QuartzBookPack.framework; + remoteRef = 7F4BB5041FB1DEC300663D5F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7F4BB5071FB1DEC300663D5F /* QuartzBookPackTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = QuartzBookPackTests.xctest; + remoteRef = 7F4BB5061FB1DEC300663D5F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework.static; diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index ddb63881c..3ba8a27b5 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -6,18 +6,18 @@ * LICENSE file in the root directory of this source tree. */ #import +#import #import "RNSVGTSpan.h" #import "RNSVGText.h" #import "RNSVGTextPath.h" -#import "UIBezierPath+Text.h" #import "FontData.h" @implementation RNSVGTSpan { CGFloat startOffset; - UIBezierPath *_path; + UIBezierPath *_bezierPath; CGPathRef _cache; - CGFloat pathLength; + CGFloat _pathLength; RNSVGTextPath * textPath; RNSVGPath * textPathPath; bool isClosed; @@ -282,11 +282,11 @@ vertical alternates (OpenType feature: vert) must be enabled. double textMeasure = CGRectGetWidth(textBounds); double offset = getTextAnchorOffset(textAnchor, textMeasure); - bool hasTextPath = _path != nil; + bool hasTextPath = _bezierPath != nil; int side = 1; double startOfRendering = 0; - double endOfRendering = pathLength; + double endOfRendering = _pathLength; double fontSize = [gc getFontSize]; bool sharpMidLine = false; if (hasTextPath) { @@ -351,15 +351,15 @@ For the start (end) value, the text is rendered from the start (end) of the line the path is reached. */ double absoluteStartOffset = [PropHelper fromRelativeWithNSString:textPath.startOffset - relative:pathLength + relative:_pathLength offset:0 scale:1 fontSize:fontSize]; offset += absoluteStartOffset; if (isClosed) { - double halfPathDistance = pathLength / 2; + double halfPathDistance = _pathLength / 2; startOfRendering = absoluteStartOffset + (textAnchor == TextAnchorMiddle ? -halfPathDistance : 0); - endOfRendering = startOfRendering + pathLength; + endOfRendering = startOfRendering + _pathLength; } /* TextPathSpacing spacing = textPath.getSpacing(); @@ -773,8 +773,8 @@ A negative value is an error (see Error processing). } CGPoint slope; - CGFloat percentConsumed = midPoint / pathLength; - CGPoint mid = [_path pointAtPercent:percentConsumed withSlope:&slope]; + CGFloat percentConsumed = midPoint / _pathLength; + CGPoint mid = [_bezierPath pointAtPercent:percentConsumed withSlope:&slope]; // Calculate the rotation double angle = atan2(slope.y, slope.x); @@ -816,17 +816,17 @@ CGFloat getTextAnchorOffset(enum TextAnchor textAnchor, CGFloat width) - (void)setupTextPath:(CGContextRef)context { - _path = nil; + _bezierPath = nil; textPath = nil; textPathPath = nil; [self traverseTextSuperviews:^(__kindof RNSVGText *node) { if ([node class] == [RNSVGTextPath class]) { textPath = (RNSVGTextPath*) node; textPathPath = [textPath getPath]; - _path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; - _path = [_path bezierPathByFlatteningPathAndImmutable:YES]; - pathLength = [_path pathLength]; - isClosed = [_path isClosed]; + _bezierPath = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; + _bezierPath = [_bezierPath bezierPathByFlatteningPathAndImmutable:YES]; + _pathLength = _bezierPath.pathLength; + isClosed = [_bezierPath isClosed]; return NO; } return YES; From bd189437a46e3f77c17aec0a2ee39c9aa21ad844 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Tue, 7 Nov 2017 23:55:06 +0800 Subject: [PATCH 194/198] Remove unused linker flags --- ios/RNSVG.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index a9ac60b6c..3044c51ec 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -933,8 +933,6 @@ ); LIBRARY_SEARCH_PATHS = ""; OTHER_LDFLAGS = ( - "-ObjC++", - "-lstdc++", "-all_load", "-ObjC", ); @@ -959,8 +957,6 @@ ); LIBRARY_SEARCH_PATHS = ""; OTHER_LDFLAGS = ( - "-ObjC++", - "-lstdc++", "-all_load", "-ObjC", ); From ebeba450c8f848dcb2877ccd006c161a481e2a92 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Wed, 8 Nov 2017 00:38:05 +0800 Subject: [PATCH 195/198] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 713606504..083b1035e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "6.0.0-rc10", + "version": "6.0.0-rc12", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { From 5b2972f8f654edc069b2ab420f94a763bce4e768 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Wed, 8 Nov 2017 01:14:45 +0800 Subject: [PATCH 196/198] Fix Symbol position --- ios/Elements/RNSVGSymbol.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/Elements/RNSVGSymbol.m b/ios/Elements/RNSVGSymbol.m index 125e583a8..305e2f72d 100644 --- a/ios/Elements/RNSVGSymbol.m +++ b/ios/Elements/RNSVGSymbol.m @@ -80,13 +80,14 @@ - (void)renderTo:(CGContextRef)context - (void)renderSymbolTo:(CGContextRef)context width:(CGFloat)width height:(CGFloat)height { if (self.align) { - CGRect eRect = CGRectMake([self getContextLeft], [self getContextTop], width, height); + CGRect eRect = CGRectMake(0, 0, width, height); CGAffineTransform viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) eRect:eRect align:self.align meetOrSlice:self.meetOrSlice fromSymbol:YES]; + CGContextConcatCTM(context, viewBoxTransform); } [self renderGroupTo:context]; From 75786787e9cbc68616c6267d603cea535f7f30fc Mon Sep 17 00:00:00 2001 From: Horcrux Date: Wed, 8 Nov 2017 01:34:53 +0800 Subject: [PATCH 197/198] Fix Image in iOS --- ios/Elements/RNSVGImage.m | 7 ++----- ios/Elements/RNSVGSvgView.m | 3 +-- ios/Elements/RNSVGSymbol.m | 3 +-- ios/Utils/RNSVGViewBox.h | 2 +- ios/Utils/RNSVGViewBox.m | 2 +- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m index 9f3764a87..66b750f87 100644 --- a/ios/Elements/RNSVGImage.m +++ b/ios/Elements/RNSVGImage.m @@ -129,11 +129,8 @@ - (void)renderLayerTo:(CGContextRef)context CGRect vbRect = CGRectMake(0, 0, CGRectGetWidth(renderRect), CGRectGetHeight(renderRect)); CGRect eRect = CGRectMake(canvasLeft, canvasTop, rectWidth, rectHeight); - CGAffineTransform transform = [RNSVGViewBox getTransform:vbRect eRect:eRect align:self.align meetOrSlice:self.meetOrSlice fromSymbol:NO]; + CGAffineTransform transform = [RNSVGViewBox getTransform:vbRect eRect:eRect align:self.align meetOrSlice:self.meetOrSlice]; - CGFloat dx = rectX + canvasLeft; - CGFloat dy = rectY + canvasTop; - renderRect = CGRectApplyAffineTransform(renderRect, CGAffineTransformMakeTranslation(-dx, -dy)); renderRect = CGRectApplyAffineTransform(renderRect, transform); [self clip:context]; @@ -150,7 +147,7 @@ - (CGRect)getRect:(CGContextRef)context CGFloat y = [self relativeOnHeight:self.y]; CGFloat width = [self relativeOnWidth:self.width]; CGFloat height = [self relativeOnHeight:self.height]; - return CGRectMake(x, y, x + width, y + height); + return CGRectMake(x, y, width, height); } - (CGPathRef)getPath:(CGContextRef)context diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index a89149012..46065fac5 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -115,8 +115,7 @@ - (void)drawRect:(CGRect)rect _viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) eRect:rect align:self.align - meetOrSlice:self.meetOrSlice - fromSymbol:NO]; + meetOrSlice:self.meetOrSlice]; CGContextConcatCTM(context, _viewBoxTransform); } diff --git a/ios/Elements/RNSVGSymbol.m b/ios/Elements/RNSVGSymbol.m index 305e2f72d..d4fcfc34a 100644 --- a/ios/Elements/RNSVGSymbol.m +++ b/ios/Elements/RNSVGSymbol.m @@ -85,8 +85,7 @@ - (void)renderSymbolTo:(CGContextRef)context width:(CGFloat)width height:(CGFloa CGAffineTransform viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) eRect:eRect align:self.align - meetOrSlice:self.meetOrSlice - fromSymbol:YES]; + meetOrSlice:self.meetOrSlice]; CGContextConcatCTM(context, viewBoxTransform); } diff --git a/ios/Utils/RNSVGViewBox.h b/ios/Utils/RNSVGViewBox.h index 9be8eefba..746ae79f4 100644 --- a/ios/Utils/RNSVGViewBox.h +++ b/ios/Utils/RNSVGViewBox.h @@ -11,6 +11,6 @@ @interface RNSVGViewBox : NSObject -+ (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice fromSymbol:(BOOL)fromSymbol; ++ (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice; @end diff --git a/ios/Utils/RNSVGViewBox.m b/ios/Utils/RNSVGViewBox.m index dee14d8c7..3c01208b5 100644 --- a/ios/Utils/RNSVGViewBox.m +++ b/ios/Utils/RNSVGViewBox.m @@ -12,7 +12,7 @@ @implementation RNSVGViewBox -+ (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice fromSymbol:(BOOL)fromSymbol ++ (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice { // based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform From 7474c909571ef9569d71c759d942b3d7f30c9d04 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Wed, 8 Nov 2017 12:25:39 +0800 Subject: [PATCH 198/198] Fix image render --- .../java/com/horcrux/svg/ImageShadowNode.java | 41 ++++++++++--------- ios/Elements/RNSVGImage.m | 34 ++++++--------- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java index 4e29c5959..13777e271 100644 --- a/android/src/main/java/com/horcrux/svg/ImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -50,7 +50,8 @@ class ImageShadowNode extends RenderableShadowNode { private String mW; private String mH; private Uri mUri; - private float mImageRatio; + private int mImageWidth; + private int mImageHeight; private String mAlign; private int mMeetOrSlice; private final AtomicBoolean mLoading = new AtomicBoolean(false); @@ -97,9 +98,11 @@ public void setSrc(@Nullable ReadableMap src) { } if (src.hasKey("width") && src.hasKey("height")) { - mImageRatio = (float)src.getInt("width") / (float)src.getInt("height"); + mImageWidth = src.getInt("width"); + mImageHeight = src.getInt("height"); } else { - mImageRatio = 0f; + mImageWidth = 0; + mImageHeight = 0; } mUri = Uri.parse(uriString); } @@ -198,32 +201,32 @@ private void doRender(Canvas canvas, Paint paint, Bitmap bitmap, float opacity) float rectHeight = (float)rect.height(); float rectX = (float)rect.left; float rectY = (float)rect.top; - float rectRatio = rectWidth / rectHeight; - RectF renderRect; + float canvasLeft = getCanvasLeft(); + float canvasTop = getCanvasTop(); - if (mImageRatio == 0f || mImageRatio == rectRatio) { - renderRect = new RectF(rect); - } else if (mImageRatio < rectRatio) { - renderRect = new RectF(0, 0, (int)(rectHeight * mImageRatio), (int)rectHeight); - } else { - renderRect = new RectF(0, 0, (int)rectWidth, (int)(rectWidth / mImageRatio)); + if (mImageWidth == 0 || mImageHeight == 0) { + mImageWidth = bitmap.getWidth(); + mImageHeight = bitmap.getHeight(); } - float canvasLeft = getCanvasLeft(); - float canvasTop = getCanvasTop(); - RectF vbRect = new RectF(0, 0, renderRect.width() / mScale, renderRect.height() / mScale); - RectF eRect = new RectF(canvasLeft, canvasTop, rectWidth / mScale + canvasLeft, rectHeight / mScale + canvasTop); + RectF renderRect = new RectF(0, 0, mImageWidth, mImageHeight); + + RectF vbRect = new RectF(0, 0, mImageWidth, mImageHeight); + RectF eRect = new RectF(canvasLeft, canvasTop, (rectWidth / mScale) + canvasLeft, (rectHeight / mScale) + canvasTop); Matrix transform = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); Matrix translation = new Matrix(); + transform.mapRect(renderRect); if (mMatrix != null) { translation.postConcat(mMatrix); + //mMatrix.mapRect(renderRect); } - float dx = rectX + canvasLeft; - float dy = rectY + canvasTop; - translation.postTranslate(-dx, -dy); + float dx = rectX / mScale + canvasLeft; + float dy = rectY / mScale + canvasTop; + translation.postTranslate(dx, dy); + translation.postScale(mScale, mScale); translation.mapRect(renderRect); - transform.mapRect(renderRect); + Path clip = new Path(); diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m index 66b750f87..fed48df22 100644 --- a/ios/Elements/RNSVGImage.m +++ b/ios/Elements/RNSVGImage.m @@ -15,7 +15,7 @@ @implementation RNSVGImage { CGImageRef _image; - CGFloat _imageRatio; + CGSize _imageSize; } - (void)setSrc:(id)src @@ -25,14 +25,13 @@ - (void)setSrc:(id)src } _src = src; CGImageRelease(_image); + _image = CGImageRetain([RCTConvert CGImage:src]); RCTImageSource *source = [RCTConvert RCTImageSource:src]; if (source.size.width != 0 && source.size.height != 0) { - _imageRatio = source.size.width / source.size.height; + _imageSize = source.size; } else { - _imageRatio = 0.0; + _imageSize = CGSizeMake(CGImageGetWidth(_image), CGImageGetHeight(_image)); } - - _image = CGImageRetain([RCTConvert CGImage:src]); [self invalidate]; } @@ -108,31 +107,24 @@ - (void)renderLayerTo:(CGContextRef)context CGContextScaleCTM(context, 1, -1); // apply viewBox transform on Image render. - CGFloat imageRatio = _imageRatio; + CGRect renderRect = CGRectMake(0, 0, _imageSize.width, _imageSize.height); + CGFloat rectWidth = CGRectGetWidth(rect); CGFloat rectHeight = CGRectGetHeight(rect); CGFloat rectX = CGRectGetMinX(rect); CGFloat rectY = CGRectGetMinY(rect); - CGFloat rectRatio = rectWidth / rectHeight; - CGRect renderRect; - - if (!imageRatio || imageRatio == rectRatio) { - renderRect = rect; - } else if (imageRatio < rectRatio) { - renderRect = CGRectMake(0, 0, rectHeight * imageRatio, rectHeight); - } else { - renderRect = CGRectMake(0, 0, rectWidth, rectWidth / imageRatio); - } - CGFloat canvasLeft = [self getContextLeft]; CGFloat canvasTop = [self getContextTop]; - CGRect vbRect = CGRectMake(0, 0, CGRectGetWidth(renderRect), CGRectGetHeight(renderRect)); + CGRect eRect = CGRectMake(canvasLeft, canvasTop, rectWidth, rectHeight); - + CGRect vbRect = CGRectMake(0, 0, CGRectGetWidth(renderRect), CGRectGetHeight(renderRect)); CGAffineTransform transform = [RNSVGViewBox getTransform:vbRect eRect:eRect align:self.align meetOrSlice:self.meetOrSlice]; - renderRect = CGRectApplyAffineTransform(renderRect, transform); - + + CGFloat dx = rectX + canvasLeft; + CGFloat dy = rectY + canvasTop; + renderRect = CGRectApplyAffineTransform(renderRect, CGAffineTransformMakeTranslation(dx, dy)); + [self clip:context]; CGContextClipToRect(context, rect);