├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── index.js
├── injectedHtml
└── index.js
├── injectedJavaScript
├── application.js
├── errorHandler.js
├── executeNativeFunction.js
└── signaturePad.js
├── package.json
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "env": {
5 | "browser": true,
6 | "jest": true
7 | },
8 | "rules": {
9 | "object-curly-newline": 0,
10 | "object-curly-spacing": 0,
11 | "max-len": 0,
12 | "react/require-default-props": 0,
13 | "react/prop-types": 0,
14 | "react/jsx-tag-spacing": 0,
15 | "comma-dangle": 0,
16 | "no-use-before-define": 0,
17 | "import/prefer-default-export": 0,
18 | "prefer-promise-reject-errors": 0,
19 | "no-trailing-spaces": [0],
20 | "padded-blocks": [0],
21 | "no-restricted-globals": [0],
22 | "react/jsx-filename-extension": 0,
23 | "jsx-a11y/media-has-caption": 0,
24 | "jsx-a11y/anchor-is-valid": 0,
25 | "react/no-did-update-set-state": 0,
26 | "no-console": "off",
27 | "react/destructuring-assignment": [1, "always", { "ignoreClassFields": true }]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | .idea
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 DispatcherInc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-signature-pad
2 |
3 | [](//npmjs.com/package/react-native-signature-pad)
4 | [](https://github.com/kevinstumpf/react-native-signature-pad) [](http://makeapullrequest.com) [](#contributors) [](https://snyk.io/test/github/kevinstumpf/react-native-signature-pad)
5 |
6 | React Native wrapper around @[szimek's](https://github.com/szimek) HTML5 Canvas based [Signature Pad](https://github.com/szimek/signature_pad)
7 |
8 | - Supports Android and iOS
9 | - Pure JavaScript implementation with no native dependencies
10 | - Tested with RN 0.20
11 | - Can easily be rotated using the "transform" style
12 | - Generates a base64 encoded png image of the signature
13 |
14 | ## Demo
15 |
16 |  
17 |
18 | ## Installation
19 |
20 | ```sh
21 | $ yarn add react-native-signature-pad
22 | ```
23 |
24 | ## Example
25 |
26 | ```js
27 | import React, {Component} from 'react';
28 | import {View} from 'react-native';
29 | import SignaturePad from 'react-native-signature-pad';
30 |
31 | export default class Demo extends Component {
32 | render = () => {
33 | return (
34 |
35 |
38 |
39 | )
40 | };
41 |
42 | _signaturePadError = (error) => {
43 | console.error(error);
44 | };
45 |
46 | _signaturePadChange = ({base64DataUrl}) => {
47 | console.log("Got new signature: " + base64DataUrl);
48 | };
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {StyleSheet, View, ViewPropTypes} from 'react-native';
4 | import {WebView} from 'react-native-webview';
5 |
6 |
7 | import htmlContent from './injectedHtml';
8 | import injectedSignaturePad from './injectedJavaScript/signaturePad';
9 | import injectedApplication from './injectedJavaScript/application';
10 | import injectedErrorHandler from './injectedJavaScript/errorHandler';
11 | import injectedExecuteNativeFunction from './injectedJavaScript/executeNativeFunction';
12 |
13 | export default class SignaturePad extends Component {
14 |
15 | static propTypes = {
16 | onChange: PropTypes.func,
17 | onError: PropTypes.func,
18 | style: ViewPropTypes.style,
19 | penColor: PropTypes.string,
20 | dataURL: PropTypes.string
21 | };
22 |
23 | static defaultProps = {
24 | onChange: () => {
25 | },
26 | onError: () => {
27 |
28 | },
29 | style: {}
30 | };
31 |
32 | constructor(props) {
33 | super(props);
34 | this.state = {base64DataUrl: props.dataURL || null};
35 | const { backgroundColor } = StyleSheet.flatten(props.style);
36 | var injectedJavaScript = injectedExecuteNativeFunction
37 | + injectedErrorHandler
38 | + injectedSignaturePad
39 | + injectedApplication(props.penColor, backgroundColor, props.dataURL);
40 | var html = htmlContent(injectedJavaScript);
41 | this.source = {html}; //We don't use WebView's injectedJavaScript because on Android, the WebView re-injects the JavaScript upon every url change. Given that we use url changes to communicate signature changes to the React Native app, the JS is re-injected every time a stroke is drawn.
42 | }
43 |
44 | _onNavigationChange = (args) => {
45 | this._parseMessageFromWebViewNavigationChange(unescape(args.url));
46 | };
47 |
48 | _parseMessageFromWebViewNavigationChange = (newUrl) => {
49 | //Example input:
50 | //applewebdata://4985ECDA-4C2B-4E37-87ED-0070D14EB985#executeFunction=jsError&arguments=%7B%22message%22:%22ReferenceError:%20Can't%20find%20variable:%20WHADDUP%22,%22url%22:%22applewebdata://4985ECDA-4C2B-4E37-87ED-0070D14EB985%22,%22line%22:340,%22column%22:10%7D"
51 | //All parameters to the native world are passed via a hash url where every parameter is passed as &[ParameterName]<-[Content]&
52 | var hashUrlIndex = newUrl.lastIndexOf('#');
53 | if(hashUrlIndex === -1) {
54 | return;
55 | }
56 |
57 | var hashUrl = newUrl.substring(hashUrlIndex);
58 | hashUrl = decodeURIComponent(hashUrl);
59 | var regexFindAllSubmittedParameters = /&(.*?)&/g;
60 |
61 | var parameters = {};
62 | var parameterMatch = regexFindAllSubmittedParameters.exec(hashUrl);
63 | if(!parameterMatch) {
64 | return;
65 | }
66 |
67 | while(parameterMatch) {
68 | var parameterPair = parameterMatch[1]; //For example executeFunction=jsError or arguments=...
69 |
70 | var parameterPairSplit = parameterPair.split('<-');
71 | if(parameterPairSplit.length === 2) {
72 | parameters[parameterPairSplit[0]] = parameterPairSplit[1];
73 | }
74 |
75 | parameterMatch = regexFindAllSubmittedParameters.exec(hashUrl);
76 | }
77 |
78 | if(!this._attemptToExecuteNativeFunctionFromWebViewMessage(parameters)) {
79 | logger.warn({parameters, hashUrl}, 'Received an unknown set of parameters from WebView');
80 | }
81 | };
82 |
83 | _attemptToExecuteNativeFunctionFromWebViewMessage = (message) => {
84 | if(message.executeFunction && message.arguments) {
85 | var parsedArguments = JSON.parse(message.arguments);
86 |
87 | var referencedFunction = this['_bridged_' + message.executeFunction];
88 | if(typeof(referencedFunction) === 'function') {
89 | referencedFunction.apply(this, [parsedArguments]);
90 | return true;
91 | }
92 | }
93 |
94 | return false;
95 | };
96 |
97 | _bridged_jsError = (args) => {
98 | this.props.onError({details: args});
99 | };
100 |
101 | _bridged_finishedStroke = ({base64DataUrl}) => {
102 | this.props.onChange({base64DataUrl});
103 | this.setState({base64DataUrl});
104 | };
105 |
106 | _renderError = (args) => {
107 | this.props.onError({details: args});
108 | };
109 |
110 | _renderLoading = (args) => {
111 |
112 | };
113 |
114 | onMessage = (event) => {
115 | var base64DataUrl = JSON.parse(event.nativeEvent.data);
116 | this._bridged_finishedStroke(base64DataUrl);
117 | }
118 |
119 | render = () => {
120 | return (
121 |
129 | )
130 | };
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/injectedHtml/index.js:
--------------------------------------------------------------------------------
1 | var content = script =>
2 | `
3 |
4 |
22 |
23 |
24 |
27 |
28 | `;
29 |
30 | export default content;
31 |
--------------------------------------------------------------------------------
/injectedJavaScript/application.js:
--------------------------------------------------------------------------------
1 | var content = (penColor, backgroundColor, dataURL) => `
2 |
3 | var showSignaturePad = function (signaturePadCanvas, bodyWidth, bodyHeight) {
4 | /*We're rotating by 90% -> Flip X and Y*/
5 | /*var width = bodyHeight;
6 | var height = bodyWidth;*/
7 |
8 | var width = bodyWidth;
9 | var height = bodyHeight;
10 |
11 | var sizeSignaturePad = function () {
12 | var devicePixelRatio = 1; /*window.devicePixelRatio || 1;*/
13 | var canvasWidth = width * devicePixelRatio;
14 | var canvasHeight = height * devicePixelRatio;
15 | signaturePadCanvas.width = canvasWidth;
16 | signaturePadCanvas.height = canvasHeight;
17 | signaturePadCanvas.getContext('2d').scale(devicePixelRatio, devicePixelRatio);
18 | };
19 |
20 | var finishedStroke = function(base64DataUrl) {
21 | executeNativeFunction('finishedStroke', {base64DataUrl: base64DataUrl});
22 | };
23 |
24 | var enableSignaturePadFunctionality = function () {
25 | var signaturePad = new SignaturePad(signaturePadCanvas, {
26 | penColor: '${penColor || 'black'}',
27 | backgroundColor: '${backgroundColor || 'white'}',
28 | onEnd: function() { finishedStroke(signaturePad.toDataURL()); }
29 | });
30 | /* signaturePad.translateMouseCoordinates = function (point) {
31 | var translatedY = point.x;
32 | var translatedX = width - point.y;
33 | point.x = translatedX;
34 | point.y = translatedY;
35 | }; */
36 | signaturePad.minWidth = 1;
37 | signaturePad.maxWidth = 4;
38 | if ('${dataURL}') {
39 | signaturePad.fromDataURL('${dataURL}');
40 | }
41 | };
42 |
43 | sizeSignaturePad();
44 | enableSignaturePadFunctionality();
45 | };
46 |
47 |
48 | var bodyWidth = window.innerWidth;
49 | var bodyHeight = window.innerHeight;
50 |
51 | var canvasElement = document.querySelector('canvas');
52 | showSignaturePad(canvasElement, bodyWidth, bodyHeight);
53 | `;
54 |
55 | export default content;
56 |
--------------------------------------------------------------------------------
/injectedJavaScript/errorHandler.js:
--------------------------------------------------------------------------------
1 | var content = `
2 | window.onerror = function(message, url, line, column, error) {
3 | executeNativeFunction('jsError', {message: message, url: url, line: line, column: column});
4 | };
5 | `;
6 |
7 | export default content;
--------------------------------------------------------------------------------
/injectedJavaScript/executeNativeFunction.js:
--------------------------------------------------------------------------------
1 | var content = `
2 | function executeNativeFunction(fnName, args) {
3 | window.location.hash = '&executeFunction<-' + fnName + '&' + '&arguments<-' + JSON.stringify(args) + '&' + window.ReactNativeWebView.postMessage(JSON.stringify(args)) + '&';
4 | }
5 | `;
6 |
7 | export default content;
8 |
--------------------------------------------------------------------------------
/injectedJavaScript/signaturePad.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Signature Pad v1.3.2
3 | * https://github.com/szimek/signature_pad
4 | *
5 | * Copyright 2013 Szymon Nowak
6 | * Released under the MIT license
7 | *
8 | * The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
9 | * http://corner.squareup.com/2012/07/smoother-signatures.html
10 | *
11 | * Implementation of interpolation using cubic Bézier curves is taken from:
12 | * http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
13 | *
14 | * Algorithm for approximated length of a Bézier curve is taken from:
15 | * http://www.lemoda.net/maths/bezier-length/index.html
16 | *
17 | */
18 | var content = `var SignaturePad = (function (document) {
19 | "use strict";
20 |
21 | var SignaturePad = function (canvas, options) {
22 | var self = this,
23 | opts = options || {};
24 |
25 | this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
26 | this.minWidth = opts.minWidth || 0.5;
27 | this.maxWidth = opts.maxWidth || 2.5;
28 | this.dotSize = opts.dotSize || function () {
29 | return (this.minWidth + this.maxWidth) / 2;
30 | };
31 | this.penColor = opts.penColor || "black";
32 | this.backgroundColor = opts.backgroundColor || "rgba(0,0,0,0)";
33 | this.onEnd = opts.onEnd;
34 | this.onBegin = opts.onBegin;
35 |
36 | this.pointWasOutOfCanvas = false;
37 |
38 | this._canvas = canvas;
39 | this._ctx = canvas.getContext("2d");
40 | this.clear();
41 |
42 | this._handleMouseEvents();
43 | this._handleTouchEvents();
44 | };
45 |
46 | SignaturePad.prototype.clear = function () {
47 | var ctx = this._ctx,
48 | canvas = this._canvas;
49 |
50 | ctx.fillStyle = this.backgroundColor;
51 | ctx.clearRect(0, 0, canvas.width, canvas.height);
52 | ctx.fillRect(0, 0, canvas.width, canvas.height);
53 | this._reset();
54 | };
55 |
56 | SignaturePad.prototype.toDataURL = function (imageType, quality) {
57 | var canvas = this._canvas;
58 | return canvas.toDataURL.apply(canvas, arguments);
59 | };
60 |
61 | SignaturePad.prototype.fromDataURL = function (dataUrl) {
62 | var self = this,
63 | image = new Image();
64 |
65 | this._reset();
66 | image.src = dataUrl;
67 | image.onload = function () {
68 | self._ctx.drawImage(image, 0, 0, self._canvas.width, self._canvas.height);
69 | };
70 | this._isEmpty = false;
71 | };
72 |
73 | SignaturePad.prototype._strokeUpdate = function (event) {
74 | var point = this._createPoint(event);
75 | this._addPoint(point);
76 | };
77 |
78 | SignaturePad.prototype._strokeBegin = function (event) {
79 | this._reset();
80 | this._strokeUpdate(event);
81 | if (typeof this.onBegin === 'function') {
82 | this.onBegin(event);
83 | }
84 | };
85 |
86 | SignaturePad.prototype._strokeDraw = function (point) {
87 | var ctx = this._ctx,
88 | dotSize = typeof(this.dotSize) === 'function' ? this.dotSize() : this.dotSize;
89 |
90 | ctx.beginPath();
91 | this._drawPoint(point.x, point.y, dotSize);
92 | ctx.closePath();
93 | ctx.fill();
94 | };
95 |
96 | SignaturePad.prototype._strokeEnd = function (event) {
97 | var canDrawCurve = this.points.length > 2,
98 | point = this.points[0];
99 |
100 | if (!canDrawCurve && point) {
101 | this._strokeDraw(point);
102 | }
103 | if (typeof this.onEnd === 'function') {
104 | this.onEnd(event);
105 | }
106 | };
107 |
108 | SignaturePad.prototype._handleMouseEvents = function () {
109 | var self = this;
110 | this._mouseButtonDown = false;
111 |
112 | this._canvas.addEventListener("mousedown", function (event) {
113 | if (event.which === 1) {
114 | self._mouseButtonDown = true;
115 | self._strokeBegin(event);
116 | }
117 | });
118 |
119 | this._canvas.addEventListener("mousemove", function (event) {
120 | if (self._mouseButtonDown) {
121 | self._strokeUpdate(event);
122 | }
123 | });
124 |
125 | document.addEventListener("mouseup", function (event) {
126 | if (event.which === 1 && self._mouseButtonDown) {
127 | self._mouseButtonDown = false;
128 | self._strokeEnd(event);
129 | }
130 | });
131 | };
132 |
133 | SignaturePad.prototype._handleTouchEvents = function () {
134 | var self = this;
135 |
136 | /* Pass touch events to canvas element on mobile IE. */
137 | this._canvas.style.msTouchAction = 'none';
138 |
139 | this._canvas.addEventListener("touchstart", function (event) {
140 | var touch = event.changedTouches[0];
141 | self._strokeBegin(touch);
142 | });
143 |
144 | this._canvas.addEventListener("touchmove", function (event) {
145 | /* Prevent scrolling. */
146 | event.preventDefault();
147 |
148 | var touch = event.changedTouches[0];
149 | var rect = self._canvas.getBoundingClientRect();
150 | var x = touch.clientX - rect.left;
151 | var y = touch.clientY - rect.top;
152 |
153 | var pointOutOfCanvas = (x < 0 || y < 0 || x > rect.width || y > rect.height); // checking if point is outside of canvas
154 | if (pointOutOfCanvas) {
155 | if (!self.pointWasOutOfCanvas) {
156 | // if the previous point was inside of canvas and the new point is outside
157 | // call onEnd to capture the signature
158 | self.onEnd();
159 | }
160 | // set the pointWasOutOfCanvas to true to indicate that user has drawn out of canvas
161 | self.pointWasOutOfCanvas = true;
162 | } else {
163 | // if the point is still inside canvas call _stokeUpdate as normal
164 | self.pointWasOutOfCanvas = false;
165 | self._strokeUpdate(touch);
166 | }
167 | });
168 |
169 | document.addEventListener("touchend", function (event) {
170 | var wasCanvasTouched = event.target === self._canvas;
171 | if (wasCanvasTouched) {
172 | self._strokeEnd(event);
173 | }
174 | });
175 | };
176 |
177 | SignaturePad.prototype.isEmpty = function () {
178 | return this._isEmpty;
179 | };
180 |
181 | SignaturePad.prototype._reset = function () {
182 | this.points = [];
183 | this._lastVelocity = 0;
184 | this._lastWidth = (this.minWidth + this.maxWidth) / 2;
185 | this._isEmpty = true;
186 | this._ctx.fillStyle = this.penColor;
187 | };
188 |
189 | SignaturePad.prototype._createPoint = function (event) {
190 | var rect = this._canvas.getBoundingClientRect();
191 | var point = new Point(
192 | event.clientX - rect.left,
193 | event.clientY - rect.top
194 | );
195 |
196 | if(this.translateMouseCoordinates)
197 | this.translateMouseCoordinates(point);
198 |
199 | return point;
200 | };
201 |
202 | SignaturePad.prototype._addPoint = function (point) {
203 | var points = this.points,
204 | c2, c3,
205 | curve, tmp;
206 |
207 | points.push(point);
208 |
209 | if (points.length > 2) {
210 | /* To reduce the initial lag make it work with 3 points
211 | by copying the first point to the beginning. */
212 | if (points.length === 3) points.unshift(points[0]);
213 |
214 | tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
215 | c2 = tmp.c2;
216 | tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
217 | c3 = tmp.c1;
218 | curve = new Bezier(points[1], c2, c3, points[2]);
219 | this._addCurve(curve);
220 |
221 | /* Remove the first element from the list,
222 | so that we always have no more than 4 points in points array. */
223 | points.shift();
224 | }
225 | };
226 |
227 | SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
228 | var dx1 = s1.x - s2.x, dy1 = s1.y - s2.y,
229 | dx2 = s2.x - s3.x, dy2 = s2.y - s3.y,
230 |
231 | m1 = {x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0},
232 | m2 = {x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0},
233 |
234 | l1 = Math.sqrt(dx1*dx1 + dy1*dy1),
235 | l2 = Math.sqrt(dx2*dx2 + dy2*dy2),
236 |
237 | dxm = (m1.x - m2.x),
238 | dym = (m1.y - m2.y),
239 |
240 | k = l2 / (l1 + l2),
241 | cm = {x: m2.x + dxm*k, y: m2.y + dym*k},
242 |
243 | tx = s2.x - cm.x,
244 | ty = s2.y - cm.y;
245 |
246 | return {
247 | c1: new Point(m1.x + tx, m1.y + ty),
248 | c2: new Point(m2.x + tx, m2.y + ty)
249 | };
250 | };
251 |
252 | SignaturePad.prototype._addCurve = function (curve) {
253 | var startPoint = curve.startPoint,
254 | endPoint = curve.endPoint,
255 | velocity, newWidth;
256 |
257 | velocity = endPoint.velocityFrom(startPoint);
258 | velocity = this.velocityFilterWeight * velocity
259 | + (1 - this.velocityFilterWeight) * this._lastVelocity;
260 |
261 | newWidth = this._strokeWidth(velocity);
262 | this._drawCurve(curve, this._lastWidth, newWidth);
263 |
264 | this._lastVelocity = velocity;
265 | this._lastWidth = newWidth;
266 | };
267 |
268 | SignaturePad.prototype._drawPoint = function (x, y, size) {
269 | var ctx = this._ctx;
270 |
271 | ctx.moveTo(x, y);
272 | ctx.arc(x, y, size, 0, 2 * Math.PI, false);
273 | this._isEmpty = false;
274 | };
275 |
276 | SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
277 | var ctx = this._ctx,
278 | widthDelta = endWidth - startWidth,
279 | drawSteps, width, i, t, tt, ttt, u, uu, uuu, x, y;
280 |
281 | drawSteps = Math.floor(curve.length());
282 | ctx.beginPath();
283 | for (i = 0; i < drawSteps; i++) {
284 | /* Calculate the Bezier (x, y) coordinate for this step. */
285 | t = i / drawSteps;
286 | tt = t * t;
287 | ttt = tt * t;
288 | u = 1 - t;
289 | uu = u * u;
290 | uuu = uu * u;
291 |
292 | x = uuu * curve.startPoint.x;
293 | x += 3 * uu * t * curve.control1.x;
294 | x += 3 * u * tt * curve.control2.x;
295 | x += ttt * curve.endPoint.x;
296 |
297 | y = uuu * curve.startPoint.y;
298 | y += 3 * uu * t * curve.control1.y;
299 | y += 3 * u * tt * curve.control2.y;
300 | y += ttt * curve.endPoint.y;
301 |
302 | width = startWidth + ttt * widthDelta;
303 | this._drawPoint(x, y, width);
304 | }
305 | ctx.closePath();
306 | ctx.fill();
307 | };
308 |
309 | SignaturePad.prototype._strokeWidth = function (velocity) {
310 | return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
311 | };
312 |
313 |
314 | var Point = function (x, y, time) {
315 | this.x = x;
316 | this.y = y;
317 | this.time = time || new Date().getTime();
318 | };
319 |
320 | Point.prototype.velocityFrom = function (start) {
321 | return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 1;
322 | };
323 |
324 | Point.prototype.distanceTo = function (start) {
325 | return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
326 | };
327 |
328 | var Bezier = function (startPoint, control1, control2, endPoint) {
329 | this.startPoint = startPoint;
330 | this.control1 = control1;
331 | this.control2 = control2;
332 | this.endPoint = endPoint;
333 | };
334 |
335 | /* Returns approximated length. */
336 | Bezier.prototype.length = function () {
337 | var steps = 10,
338 | length = 0,
339 | i, t, cx, cy, px, py, xdiff, ydiff;
340 |
341 | for (i = 0; i <= steps; i++) {
342 | t = i / steps;
343 | cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
344 | cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
345 | if (i > 0) {
346 | xdiff = cx - px;
347 | ydiff = cy - py;
348 | length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
349 | }
350 | px = cx;
351 | py = cy;
352 | }
353 | return length;
354 | };
355 |
356 | Bezier.prototype._point = function (t, start, c1, c2, end) {
357 | return start * (1.0 - t) * (1.0 - t) * (1.0 - t)
358 | + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t
359 | + 3.0 * c2 * (1.0 - t) * t * t
360 | + end * t * t * t;
361 | };
362 |
363 | return SignaturePad;
364 | })(document);`;
365 |
366 | export default content;
367 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-signature-pad",
3 | "version": "0.1.2",
4 | "description": "React Native wrapper around szimek's Canvas based Signature Pad",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/DispatcherInc/react-native-signature-pad.git"
12 | },
13 | "keywords": [
14 | "react-native",
15 | "react-component",
16 | "ios",
17 | "android",
18 | "signature",
19 | "pad",
20 | "signaturepad",
21 | "canvas"
22 | ],
23 | "author": "Kevin Stumpf",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/DispatcherInc/react-native-signature-pad/issues"
27 | },
28 | "homepage": "https://github.com/DispatcherInc/react-native-signature-pad#readme",
29 | "peerDependencies": {
30 | "react": "^16.8.4",
31 | "react-native": "^0.58.6",
32 | "react-native-webview": "^5.3.0"
33 | },
34 | "dependencies": {
35 | "prop-types": "^15.6.0"
36 | },
37 | "devDependencies": {
38 | "babel-eslint": "10.0.1",
39 | "eslint": "^5.15.1",
40 | "eslint-config-airbnb": "^17.1.0",
41 | "eslint-plugin-import": "^2.16.0",
42 | "eslint-plugin-jsx-a11y": "^6.2.1",
43 | "eslint-plugin-react": "^7.12.4"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------