├── .babelrc
├── .bithoundrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── dist
├── svg-path-properties.cjs.js
├── svg-path-properties.esm.js
├── svg-path-properties.min.js
└── types
│ ├── arc.d.ts
│ ├── bezier-functions.d.ts
│ ├── bezier-values.d.ts
│ ├── bezier.d.ts
│ ├── index.d.ts
│ ├── linear.d.ts
│ ├── parse.d.ts
│ ├── svg-path-properties.d.ts
│ └── types.d.ts
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── arc.ts
├── bezier-functions.ts
├── bezier-values.ts
├── bezier.ts
├── index.ts
├── linear.ts
├── parse.ts
├── svg-path-properties.ts
└── types.ts
├── test
├── bezier-test.ts
├── get-parts-test.ts
├── inDelta.ts
├── index-test.ts
├── length-test.ts
├── parse-test.ts
├── point-at-test.ts
├── tangent-test.ts
└── test-getLength.html
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/typescript"],
3 | "plugins": [
4 | "@babel/proposal-class-properties",
5 | "@babel/proposal-object-rest-spread",
6 | "module:babel6-plugin-strip-class-callcheck"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.bithoundrc:
--------------------------------------------------------------------------------
1 | "ignore": [
2 | "**/deps/**",
3 | "**/node_modules/**",
4 | "**/thirdparty/**",
5 | "**/third_party/**",
6 | "**/build/**"
7 | ]
8 |
9 | "critics": {
10 | "lint": {"engine": "eslint"}
11 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions":
3 | {"sourceType": "module"},
4 |
5 | "env": {
6 | "es6": true,
7 | "browser": true
8 | },
9 |
10 | "extends": "eslint:recommended",
11 |
12 | "rules": {
13 | "no-cond-assign": 0,
14 | "no-constant-condition": 0
15 | }
16 | }
--------------------------------------------------------------------------------
/.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 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - node
4 | - v10.2.1
5 | env:
6 | - CXX=g++-4.8
7 | before_script:
8 | - npm install -g istanbul
9 | - npm install coveralls
10 | after_success:
11 | - istanbul cover --report lcovonly ./node_modules/tape/bin/tape './test/*-test.js' && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
12 | addons:
13 | apt:
14 | sources:
15 | - ubuntu-toolchain-r-test
16 | packages:
17 | - g++-4.8
18 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.defaultFormatter": "esbenp.prettier-vscode",
4 | "[javascript]": {
5 | "editor.defaultFormatter": "esbenp.prettier-vscode"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at rveciana@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Roger Veciana i Rovira
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 | [](https://travis-ci.org/rveciana/svg-path-properties)
2 | [](https://coveralls.io/github/rveciana/svg-path-properties?branch=master)
3 |
4 | # svg-path-properties
5 |
6 | Pure Javascript alternative to getPointAtLength(t) and getTotalLength() functions. Works with Canvas objects and when Node
7 |
8 | JavaScript can access to path elements properties in a browser, such as its length and the point at a given length. Unfortunately, this can't be achieved using a Canvas element or when working with node. This library can be used to replace this need. It has no dependencies on other JavaScript libraries.
9 |
10 | # INSTALL
11 |
12 | To use with npm, just type
13 |
14 | npm install svg-path-properties
15 |
16 | You can use it int he browser directly by including _svg-path-properties.min.js_ from the dist directory
17 |
18 |
19 |
20 | # USAGE
21 |
22 | The available methods are:
23 |
24 | const path = require("svg-path-properties");
25 | const properties = new path.svgPathProperties("M0,100 Q50,-50 100,100 T200,100");
26 | const length = properties.getTotalLength();
27 | const point = properties.getPointAtLength(200);
28 | const tangent = properties.getTangentAtLength(200);
29 | const allProperties = properties.getPropertiesAtLength(200);
30 | const parts = properties.getParts();
31 |
32 | ## Node:
33 |
34 | const path = require("svg-path-properties");
35 | const properties = new path.svgPathProperties("M0,100 Q50,-50 100,100 T200,100");
36 |
37 | ## Including it from an import:
38 |
39 | import { svgPathProperties } from "svg-path-properties";
40 | const properties = new svgPathProperties("M0,100 Q50,-50 100,100 T200,100");
41 |
42 | ## Including the script in the browser
43 |
44 | Once the _script_ tag has been included,
45 |
46 | const properties = new svgPathProperties.svgPathProperties("M0,100 Q50,-50 100,100 T200,100");
47 |
48 | ## Using _new_
49 |
50 | Since _svgPathProperties_ is a class, using _new_ is the correct way to initilize it. For backwards compatibility reasons, the object can be get without it:
51 |
52 | const properties = svgPathProperties("M0,100 Q50,-50 100,100 T200,100");
53 |
54 | # Some usage examples
55 |
56 | - [Drawing an animated path](http://bl.ocks.org/rveciana/209fa7efeb01f05fa4a544a76ac8ed91)
57 | - [Label positioning](http://bl.ocks.org/rveciana/bef48021e38a77a520109d2088bff9eb)
58 | - [Drawing stramlines arrows](http://bl.ocks.org/rveciana/edb1dd43f3edc5d16ecaf4839c032dec)
59 | - [Using it to get a length when in node instead of the browser](https://github.com/veltman/flubber/blob/master/src/svg.js), as in the [Flubber library](https://github.com/veltman/flubber)
60 | - [SVG animations in React Native](https://bitbucket.org/ingenuityph/react-native-svg-animations/src/master/)
61 |
62 | # Typescript
63 |
64 | The TypeScript declaration file is available too, since version 0.5.0 From version 1.0.0, the whole library has been rewritten using TypeScript, and the types are auto-generated.
65 |
66 | # CREDITS
67 |
68 | Some parts of the code are taken from other libraries or questions at StackOverflow:
69 |
70 | For Bézier curves:
71 |
72 | - http://bl.ocks.org/hnakamur/e7efd0602bfc15f66fc5, https://gist.github.com/tunght13488/6744e77c242cc7a94859
73 | - http://stackoverflow.com/questions/11854907/calculate-the-length-of-a-segment-of-a-quadratic-bezier
74 | - http://pomax.github.io/bezierinfo
75 | - Arc elements calculation: https://github.com/MadLittleMods/svg-curve-lib/tree/f07d6008a673816f4cb74a3269164b430c3a95cb
76 |
77 | For path parsing:
78 |
79 | - https://github.com/jkroso/parse-svg-path
80 |
--------------------------------------------------------------------------------
/dist/svg-path-properties.cjs.js:
--------------------------------------------------------------------------------
1 | // http://geoexamples.com/path-properties/ v1.2.0 Copyright 2023 Roger Veciana i Rovira
2 | "use strict";function t(t,n){for(var e=0;et.length)&&(n=t.length);for(var e=0,i=new Array(n);eu.length&&(t=u.length);var n=c({x:u.x0,y:u.y0},u.rx,u.ry,u.xAxisRotate,u.LargeArcFlag,u.SweepFlag,{x:u.x1,y:u.y1},t/u.length);return{x:n.x,y:n.y}})),e(this,"getTangentAtLength",(function(t){t<0?t=0:t>u.length&&(t=u.length);var n,e=.05,i=u.getPointAtLength(t);t<0?t=0:t>u.length&&(t=u.length);var r=(n=t1&&(n=Math.sqrt(c)*n,e=Math.sqrt(c)*e);var f=(Math.pow(n,2)*Math.pow(e,2)-Math.pow(n,2)*Math.pow(l.y,2)-Math.pow(e,2)*Math.pow(l.x,2))/(Math.pow(n,2)*Math.pow(l.y,2)+Math.pow(e,2)*Math.pow(l.x,2));f=f<0?0:f;var x=(r!==h?1:-1)*Math.sqrt(f),v=x*(n*l.y/e),w=x*(-e*l.x/n),L={x:Math.cos(o)*v-Math.sin(o)*w+(t.x+s.x)/2,y:Math.sin(o)*v+Math.cos(o)*w+(t.y+s.y)/2},A={x:(l.x-v)/n,y:(l.y-w)/e},d=M({x:1,y:0},A),b=M(A,{x:(-l.x-v)/n,y:(-l.y-w)/e});!h&&b>0?b-=2*Math.PI:h&&b<0&&(b+=2*Math.PI);var P=d+(b%=2*Math.PI)*a,m=n*Math.cos(P),T=e*Math.sin(P);return{x:Math.cos(o)*m-Math.sin(o)*T+L.x,y:Math.sin(o)*m+Math.cos(o)*T+L.y,ellipticalArcStartAngle:d,ellipticalArcEndAngle:d+b,ellipticalArcAngle:P,ellipticalArcCenter:L,resultantRx:n,resultantRy:e}},f=function(t,n){t=t||500;for(var e,i=0,r=[],h=[],s=n(0),a=0;a0?Math.sqrt(l*l+c):0,y=u*u+c>0?Math.sqrt(u*u+c):0,p=u+Math.sqrt(u*u+c)!==0&&(l+f)/(u+y)!=0?c*Math.log(Math.abs((l+f)/(u+y))):0;return Math.sqrt(a)/2*(l*f-u*y+p)},q=function(t,n,e){return{x:2*(1-e)*(t[1]-t[0])+2*e*(t[2]-t[1]),y:2*(1-e)*(n[1]-n[0])+2*e*(n[2]-n[1])}};function _(t,n,e){var i=S(1,e,t),r=S(1,e,n),h=i*i+r*r;return Math.sqrt(h)}var S=function t(n,e,i){var r,h,s=i.length-1;if(0===s)return 0;if(0===n){h=0;for(var a=0;a<=s;a++)h+=A[s][a]*Math.pow(1-e,s-a)*Math.pow(e,a)*i[a];return h}r=new Array(s);for(var o=0;o.001;){var a=e(r+h),o=Math.abs(t-a)/n;if(o500)break}return r},C=n((function(t,n,i,r,h,s,a,o){var g=this;e(this,"a",void 0),e(this,"b",void 0),e(this,"c",void 0),e(this,"d",void 0),e(this,"length",void 0),e(this,"getArcLength",void 0),e(this,"getPoint",void 0),e(this,"getDerivative",void 0),e(this,"getTotalLength",(function(){return g.length})),e(this,"getPointAtLength",(function(t){var n=[g.a.x,g.b.x,g.c.x,g.d.x],e=[g.a.y,g.b.y,g.c.y,g.d.y],i=N(t,g.length,(function(t){return g.getArcLength(n,e,t)}));return g.getPoint(n,e,i)})),e(this,"getTangentAtLength",(function(t){var n=[g.a.x,g.b.x,g.c.x,g.d.x],e=[g.a.y,g.b.y,g.c.y,g.d.y],i=N(t,g.length,(function(t){return g.getArcLength(n,e,t)})),r=g.getDerivative(n,e,i),h=Math.sqrt(r.x*r.x+r.y*r.y);return h>0?{x:r.x/h,y:r.y/h}:{x:0,y:0}})),e(this,"getPropertiesAtLength",(function(t){var n,e=[g.a.x,g.b.x,g.c.x,g.d.x],i=[g.a.y,g.b.y,g.c.y,g.d.y],r=N(t,g.length,(function(t){return g.getArcLength(e,i,t)})),h=g.getDerivative(e,i,r),s=Math.sqrt(h.x*h.x+h.y*h.y);n=s>0?{x:h.x/s,y:h.y/s}:{x:0,y:0};var a=g.getPoint(e,i,r);return{x:a.x,y:a.y,tangentX:n.x,tangentY:n.y}})),e(this,"getC",(function(){return g.c})),e(this,"getD",(function(){return g.d})),this.a={x:t,y:n},this.b={x:i,y:r},this.c={x:h,y:s},void 0!==a&&void 0!==o?(this.getArcLength=P,this.getPoint=d,this.getDerivative=b,this.d={x:a,y:o}):(this.getArcLength=T,this.getPoint=m,this.getDerivative=q,this.d={x:0,y:0}),this.length=this.getArcLength([this.a.x,this.b.x,this.c.x,this.d.x],[this.a.y,this.b.y,this.c.y,this.d.y],1)})),O=n((function(t){var n=this;e(this,"length",0),e(this,"partial_lengths",[]),e(this,"functions",[]),e(this,"initial_point",null),e(this,"getPartAtLength",(function(t){t<0?t=0:t>n.length&&(t=n.length);for(var e=n.partial_lengths.length-1;n.partial_lengths[e]>=t&&e>0;)e--;return e++,{fraction:t-n.partial_lengths[e-1],i:e}})),e(this,"getTotalLength",(function(){return n.length})),e(this,"getPointAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getPointAtLength(e.fraction);if(n.initial_point)return n.initial_point;throw new Error("Wrong function at this part.")})),e(this,"getTangentAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getTangentAtLength(e.fraction);if(n.initial_point)return{x:0,y:0};throw new Error("Wrong function at this part.")})),e(this,"getPropertiesAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getPropertiesAtLength(e.fraction);if(n.initial_point)return{x:n.initial_point.x,y:n.initial_point.y,tangentX:0,tangentY:0};throw new Error("Wrong function at this part.")})),e(this,"getParts",(function(){for(var t=[],e=0;e0?t:"M0,0").match(a);if(!n)throw new Error("No path elements found in string ".concat(t));return n.reduce((function(t,n){var e=n.charAt(0),r=e.toLowerCase(),h=g(n.substring(1));if("m"===r&&h.length>2&&(t.push([e].concat(i(h.splice(0,2)))),r="l",e="m"===e?"l":"L"),"a"===r.toLowerCase()&&(5===h.length||6===h.length)){var a=n.substring(1).trim().split(" ");h=[Number(a[0]),Number(a[1]),Number(a[2]),Number(a[3].charAt(0)),Number(a[3].charAt(1)),Number(a[3].substring(2)),Number(a[4])]}for(;h.length>=0;){if(h.length===s[r]){t.push([e].concat(i(h.splice(0,s[r]))));break}if(h.length0?(this.length+=r.getTotalLength(),this.functions.push(r),o=[h[y][5]+o[0],h[y][6]+o[1]]):this.functions.push(new u(o[0],o[0],o[1],o[1]));else if("S"===h[y][0]){if(y>0&&["C","c","S","s"].indexOf(h[y-1][0])>-1){if(r){var p=r.getC();r=new C(o[0],o[1],2*o[0]-p.x,2*o[1]-p.y,h[y][1],h[y][2],h[y][3],h[y][4])}}else r=new C(o[0],o[1],o[0],o[1],h[y][1],h[y][2],h[y][3],h[y][4]);r&&(this.length+=r.getTotalLength(),o=[h[y][3],h[y][4]],this.functions.push(r))}else if("s"===h[y][0]){if(y>0&&["C","c","S","s"].indexOf(h[y-1][0])>-1){if(r){var x=r.getC(),v=r.getD();r=new C(o[0],o[1],o[0]+v.x-x.x,o[1]+v.y-x.y,o[0]+h[y][1],o[1]+h[y][2],o[0]+h[y][3],o[1]+h[y][4])}}else r=new C(o[0],o[1],o[0],o[1],o[0]+h[y][1],o[1]+h[y][2],o[0]+h[y][3],o[1]+h[y][4]);r&&(this.length+=r.getTotalLength(),o=[h[y][3]+o[0],h[y][4]+o[1]],this.functions.push(r))}else if("Q"===h[y][0]){if(o[0]==h[y][1]&&o[1]==h[y][2]){var M=new u(h[y][1],h[y][3],h[y][2],h[y][4]);this.length+=M.getTotalLength(),this.functions.push(M)}else r=new C(o[0],o[1],h[y][1],h[y][2],h[y][3],h[y][4],void 0,void 0),this.length+=r.getTotalLength(),this.functions.push(r);o=[h[y][3],h[y][4]],c=[h[y][1],h[y][2]]}else if("q"===h[y][0]){if(0!=h[y][1]||0!=h[y][2])r=new C(o[0],o[1],o[0]+h[y][1],o[1]+h[y][2],o[0]+h[y][3],o[1]+h[y][4],void 0,void 0),this.length+=r.getTotalLength(),this.functions.push(r);else{var w=new u(o[0]+h[y][1],o[0]+h[y][3],o[1]+h[y][2],o[1]+h[y][4]);this.length+=w.getTotalLength(),this.functions.push(w)}c=[o[0]+h[y][1],o[1]+h[y][2]],o=[h[y][3]+o[0],h[y][4]+o[1]]}else if("T"===h[y][0]){if(y>0&&["Q","q","T","t"].indexOf(h[y-1][0])>-1)r=new C(o[0],o[1],2*o[0]-c[0],2*o[1]-c[1],h[y][1],h[y][2],void 0,void 0),this.functions.push(r),this.length+=r.getTotalLength();else{var L=new u(o[0],h[y][1],o[1],h[y][2]);this.functions.push(L),this.length+=L.getTotalLength()}c=[2*o[0]-c[0],2*o[1]-c[1]],o=[h[y][1],h[y][2]]}else if("t"===h[y][0]){if(y>0&&["Q","q","T","t"].indexOf(h[y-1][0])>-1)r=new C(o[0],o[1],2*o[0]-c[0],2*o[1]-c[1],o[0]+h[y][1],o[1]+h[y][2],void 0,void 0),this.length+=r.getTotalLength(),this.functions.push(r);else{var A=new u(o[0],o[0]+h[y][1],o[1],o[1]+h[y][2]);this.length+=A.getTotalLength(),this.functions.push(A)}c=[2*o[0]-c[0],2*o[1]-c[1]],o=[h[y][1]+o[0],h[y][2]+o[1]]}else if("A"===h[y][0]){var d=new l(o[0],o[1],h[y][1],h[y][2],h[y][3],1===h[y][4],1===h[y][5],h[y][6],h[y][7]);this.length+=d.getTotalLength(),o=[h[y][6],h[y][7]],this.functions.push(d)}else if("a"===h[y][0]){var b=new l(o[0],o[1],h[y][1],h[y][2],h[y][3],1===h[y][4],1===h[y][5],o[0]+h[y][6],o[1]+h[y][7]);this.length+=b.getTotalLength(),o=[o[0]+h[y][6],o[1]+h[y][7]],this.functions.push(b)}this.partial_lengths.push(this.length)}})),j=n((function(t){var n=this;if(e(this,"inst",void 0),e(this,"getTotalLength",(function(){return n.inst.getTotalLength()})),e(this,"getPointAtLength",(function(t){return n.inst.getPointAtLength(t)})),e(this,"getTangentAtLength",(function(t){return n.inst.getTangentAtLength(t)})),e(this,"getPropertiesAtLength",(function(t){return n.inst.getPropertiesAtLength(t)})),e(this,"getParts",(function(){return n.inst.getParts()})),this.inst=new O(t),!(this instanceof j))return new j(t)}));exports.svgPathProperties=j;
3 |
--------------------------------------------------------------------------------
/dist/svg-path-properties.esm.js:
--------------------------------------------------------------------------------
1 | // http://geoexamples.com/path-properties/ v1.2.0 Copyright 2023 Roger Veciana i Rovira
2 | function t(t,n){for(var e=0;et.length)&&(n=t.length);for(var e=0,i=new Array(n);eu.length&&(t=u.length);var n=c({x:u.x0,y:u.y0},u.rx,u.ry,u.xAxisRotate,u.LargeArcFlag,u.SweepFlag,{x:u.x1,y:u.y1},t/u.length);return{x:n.x,y:n.y}})),e(this,"getTangentAtLength",(function(t){t<0?t=0:t>u.length&&(t=u.length);var n,e=.05,i=u.getPointAtLength(t);t<0?t=0:t>u.length&&(t=u.length);var r=(n=t1&&(n=Math.sqrt(c)*n,e=Math.sqrt(c)*e);var f=(Math.pow(n,2)*Math.pow(e,2)-Math.pow(n,2)*Math.pow(l.y,2)-Math.pow(e,2)*Math.pow(l.x,2))/(Math.pow(n,2)*Math.pow(l.y,2)+Math.pow(e,2)*Math.pow(l.x,2));f=f<0?0:f;var x=(r!==h?1:-1)*Math.sqrt(f),v=x*(n*l.y/e),w=x*(-e*l.x/n),L={x:Math.cos(o)*v-Math.sin(o)*w+(t.x+a.x)/2,y:Math.sin(o)*v+Math.cos(o)*w+(t.y+a.y)/2},A={x:(l.x-v)/n,y:(l.y-w)/e},d=M({x:1,y:0},A),b=M(A,{x:(-l.x-v)/n,y:(-l.y-w)/e});!h&&b>0?b-=2*Math.PI:h&&b<0&&(b+=2*Math.PI);var m=d+(b%=2*Math.PI)*s,P=n*Math.cos(m),T=e*Math.sin(m);return{x:Math.cos(o)*P-Math.sin(o)*T+L.x,y:Math.sin(o)*P+Math.cos(o)*T+L.y,ellipticalArcStartAngle:d,ellipticalArcEndAngle:d+b,ellipticalArcAngle:m,ellipticalArcCenter:L,resultantRx:n,resultantRy:e}},f=function(t,n){t=t||500;for(var e,i=0,r=[],h=[],a=n(0),s=0;s0?Math.sqrt(l*l+c):0,y=u*u+c>0?Math.sqrt(u*u+c):0,p=u+Math.sqrt(u*u+c)!==0&&(l+f)/(u+y)!=0?c*Math.log(Math.abs((l+f)/(u+y))):0;return Math.sqrt(s)/2*(l*f-u*y+p)},q=function(t,n,e){return{x:2*(1-e)*(t[1]-t[0])+2*e*(t[2]-t[1]),y:2*(1-e)*(n[1]-n[0])+2*e*(n[2]-n[1])}};function _(t,n,e){var i=S(1,e,t),r=S(1,e,n),h=i*i+r*r;return Math.sqrt(h)}var S=function t(n,e,i){var r,h,a=i.length-1;if(0===a)return 0;if(0===n){h=0;for(var s=0;s<=a;s++)h+=A[a][s]*Math.pow(1-e,a-s)*Math.pow(e,s)*i[s];return h}r=new Array(a);for(var o=0;o.001;){var s=e(r+h),o=Math.abs(t-s)/n;if(o500)break}return r},C=n((function(t,n,i,r,h,a,s,o){var g=this;e(this,"a",void 0),e(this,"b",void 0),e(this,"c",void 0),e(this,"d",void 0),e(this,"length",void 0),e(this,"getArcLength",void 0),e(this,"getPoint",void 0),e(this,"getDerivative",void 0),e(this,"getTotalLength",(function(){return g.length})),e(this,"getPointAtLength",(function(t){var n=[g.a.x,g.b.x,g.c.x,g.d.x],e=[g.a.y,g.b.y,g.c.y,g.d.y],i=N(t,g.length,(function(t){return g.getArcLength(n,e,t)}));return g.getPoint(n,e,i)})),e(this,"getTangentAtLength",(function(t){var n=[g.a.x,g.b.x,g.c.x,g.d.x],e=[g.a.y,g.b.y,g.c.y,g.d.y],i=N(t,g.length,(function(t){return g.getArcLength(n,e,t)})),r=g.getDerivative(n,e,i),h=Math.sqrt(r.x*r.x+r.y*r.y);return h>0?{x:r.x/h,y:r.y/h}:{x:0,y:0}})),e(this,"getPropertiesAtLength",(function(t){var n,e=[g.a.x,g.b.x,g.c.x,g.d.x],i=[g.a.y,g.b.y,g.c.y,g.d.y],r=N(t,g.length,(function(t){return g.getArcLength(e,i,t)})),h=g.getDerivative(e,i,r),a=Math.sqrt(h.x*h.x+h.y*h.y);n=a>0?{x:h.x/a,y:h.y/a}:{x:0,y:0};var s=g.getPoint(e,i,r);return{x:s.x,y:s.y,tangentX:n.x,tangentY:n.y}})),e(this,"getC",(function(){return g.c})),e(this,"getD",(function(){return g.d})),this.a={x:t,y:n},this.b={x:i,y:r},this.c={x:h,y:a},void 0!==s&&void 0!==o?(this.getArcLength=m,this.getPoint=d,this.getDerivative=b,this.d={x:s,y:o}):(this.getArcLength=T,this.getPoint=P,this.getDerivative=q,this.d={x:0,y:0}),this.length=this.getArcLength([this.a.x,this.b.x,this.c.x,this.d.x],[this.a.y,this.b.y,this.c.y,this.d.y],1)})),O=n((function(t){var n=this;e(this,"length",0),e(this,"partial_lengths",[]),e(this,"functions",[]),e(this,"initial_point",null),e(this,"getPartAtLength",(function(t){t<0?t=0:t>n.length&&(t=n.length);for(var e=n.partial_lengths.length-1;n.partial_lengths[e]>=t&&e>0;)e--;return e++,{fraction:t-n.partial_lengths[e-1],i:e}})),e(this,"getTotalLength",(function(){return n.length})),e(this,"getPointAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getPointAtLength(e.fraction);if(n.initial_point)return n.initial_point;throw new Error("Wrong function at this part.")})),e(this,"getTangentAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getTangentAtLength(e.fraction);if(n.initial_point)return{x:0,y:0};throw new Error("Wrong function at this part.")})),e(this,"getPropertiesAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getPropertiesAtLength(e.fraction);if(n.initial_point)return{x:n.initial_point.x,y:n.initial_point.y,tangentX:0,tangentY:0};throw new Error("Wrong function at this part.")})),e(this,"getParts",(function(){for(var t=[],e=0;e0?t:"M0,0").match(s);if(!n)throw new Error("No path elements found in string ".concat(t));return n.reduce((function(t,n){var e=n.charAt(0),r=e.toLowerCase(),h=g(n.substring(1));if("m"===r&&h.length>2&&(t.push([e].concat(i(h.splice(0,2)))),r="l",e="m"===e?"l":"L"),"a"===r.toLowerCase()&&(5===h.length||6===h.length)){var s=n.substring(1).trim().split(" ");h=[Number(s[0]),Number(s[1]),Number(s[2]),Number(s[3].charAt(0)),Number(s[3].charAt(1)),Number(s[3].substring(2)),Number(s[4])]}for(;h.length>=0;){if(h.length===a[r]){t.push([e].concat(i(h.splice(0,a[r]))));break}if(h.length0?(this.length+=r.getTotalLength(),this.functions.push(r),o=[h[y][5]+o[0],h[y][6]+o[1]]):this.functions.push(new u(o[0],o[0],o[1],o[1]));else if("S"===h[y][0]){if(y>0&&["C","c","S","s"].indexOf(h[y-1][0])>-1){if(r){var p=r.getC();r=new C(o[0],o[1],2*o[0]-p.x,2*o[1]-p.y,h[y][1],h[y][2],h[y][3],h[y][4])}}else r=new C(o[0],o[1],o[0],o[1],h[y][1],h[y][2],h[y][3],h[y][4]);r&&(this.length+=r.getTotalLength(),o=[h[y][3],h[y][4]],this.functions.push(r))}else if("s"===h[y][0]){if(y>0&&["C","c","S","s"].indexOf(h[y-1][0])>-1){if(r){var x=r.getC(),v=r.getD();r=new C(o[0],o[1],o[0]+v.x-x.x,o[1]+v.y-x.y,o[0]+h[y][1],o[1]+h[y][2],o[0]+h[y][3],o[1]+h[y][4])}}else r=new C(o[0],o[1],o[0],o[1],o[0]+h[y][1],o[1]+h[y][2],o[0]+h[y][3],o[1]+h[y][4]);r&&(this.length+=r.getTotalLength(),o=[h[y][3]+o[0],h[y][4]+o[1]],this.functions.push(r))}else if("Q"===h[y][0]){if(o[0]==h[y][1]&&o[1]==h[y][2]){var M=new u(h[y][1],h[y][3],h[y][2],h[y][4]);this.length+=M.getTotalLength(),this.functions.push(M)}else r=new C(o[0],o[1],h[y][1],h[y][2],h[y][3],h[y][4],void 0,void 0),this.length+=r.getTotalLength(),this.functions.push(r);o=[h[y][3],h[y][4]],c=[h[y][1],h[y][2]]}else if("q"===h[y][0]){if(0!=h[y][1]||0!=h[y][2])r=new C(o[0],o[1],o[0]+h[y][1],o[1]+h[y][2],o[0]+h[y][3],o[1]+h[y][4],void 0,void 0),this.length+=r.getTotalLength(),this.functions.push(r);else{var w=new u(o[0]+h[y][1],o[0]+h[y][3],o[1]+h[y][2],o[1]+h[y][4]);this.length+=w.getTotalLength(),this.functions.push(w)}c=[o[0]+h[y][1],o[1]+h[y][2]],o=[h[y][3]+o[0],h[y][4]+o[1]]}else if("T"===h[y][0]){if(y>0&&["Q","q","T","t"].indexOf(h[y-1][0])>-1)r=new C(o[0],o[1],2*o[0]-c[0],2*o[1]-c[1],h[y][1],h[y][2],void 0,void 0),this.functions.push(r),this.length+=r.getTotalLength();else{var L=new u(o[0],h[y][1],o[1],h[y][2]);this.functions.push(L),this.length+=L.getTotalLength()}c=[2*o[0]-c[0],2*o[1]-c[1]],o=[h[y][1],h[y][2]]}else if("t"===h[y][0]){if(y>0&&["Q","q","T","t"].indexOf(h[y-1][0])>-1)r=new C(o[0],o[1],2*o[0]-c[0],2*o[1]-c[1],o[0]+h[y][1],o[1]+h[y][2],void 0,void 0),this.length+=r.getTotalLength(),this.functions.push(r);else{var A=new u(o[0],o[0]+h[y][1],o[1],o[1]+h[y][2]);this.length+=A.getTotalLength(),this.functions.push(A)}c=[2*o[0]-c[0],2*o[1]-c[1]],o=[h[y][1]+o[0],h[y][2]+o[1]]}else if("A"===h[y][0]){var d=new l(o[0],o[1],h[y][1],h[y][2],h[y][3],1===h[y][4],1===h[y][5],h[y][6],h[y][7]);this.length+=d.getTotalLength(),o=[h[y][6],h[y][7]],this.functions.push(d)}else if("a"===h[y][0]){var b=new l(o[0],o[1],h[y][1],h[y][2],h[y][3],1===h[y][4],1===h[y][5],o[0]+h[y][6],o[1]+h[y][7]);this.length+=b.getTotalLength(),o=[o[0]+h[y][6],o[1]+h[y][7]],this.functions.push(b)}this.partial_lengths.push(this.length)}})),j=n((function(t){var n=this;if(e(this,"inst",void 0),e(this,"getTotalLength",(function(){return n.inst.getTotalLength()})),e(this,"getPointAtLength",(function(t){return n.inst.getPointAtLength(t)})),e(this,"getTangentAtLength",(function(t){return n.inst.getTangentAtLength(t)})),e(this,"getPropertiesAtLength",(function(t){return n.inst.getPropertiesAtLength(t)})),e(this,"getParts",(function(){return n.inst.getParts()})),this.inst=new O(t),!(this instanceof j))return new j(t)}));export{j as svgPathProperties};
3 |
--------------------------------------------------------------------------------
/dist/svg-path-properties.min.js:
--------------------------------------------------------------------------------
1 | // http://geoexamples.com/path-properties/ v1.2.0 Copyright 2023 Roger Veciana i Rovira
2 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).svgPathProperties={})}(this,(function(t){"use strict";function n(t,n){for(var e=0;et.length)&&(n=t.length);for(var e=0,i=new Array(n);eu.length&&(t=u.length);var n=f({x:u.x0,y:u.y0},u.rx,u.ry,u.xAxisRotate,u.LargeArcFlag,u.SweepFlag,{x:u.x1,y:u.y1},t/u.length);return{x:n.x,y:n.y}})),i(this,"getTangentAtLength",(function(t){t<0?t=0:t>u.length&&(t=u.length);var n,e=.05,i=u.getPointAtLength(t);t<0?t=0:t>u.length&&(t=u.length);var r=(n=t1&&(n=Math.sqrt(c)*n,e=Math.sqrt(c)*e);var f=(Math.pow(n,2)*Math.pow(e,2)-Math.pow(n,2)*Math.pow(l.y,2)-Math.pow(e,2)*Math.pow(l.x,2))/(Math.pow(n,2)*Math.pow(l.y,2)+Math.pow(e,2)*Math.pow(l.x,2));f=f<0?0:f;var y=(r!==h?1:-1)*Math.sqrt(f),v=y*(n*l.y/e),M=y*(-e*l.x/n),L={x:Math.cos(o)*v-Math.sin(o)*M+(t.x+s.x)/2,y:Math.sin(o)*v+Math.cos(o)*M+(t.y+s.y)/2},d={x:(l.x-v)/n,y:(l.y-M)/e},A=w({x:1,y:0},d),b=w(d,{x:(-l.x-v)/n,y:(-l.y-M)/e});!h&&b>0?b-=2*Math.PI:h&&b<0&&(b+=2*Math.PI);var P=A+(b%=2*Math.PI)*a,m=n*Math.cos(P),T=e*Math.sin(P);return{x:Math.cos(o)*m-Math.sin(o)*T+L.x,y:Math.sin(o)*m+Math.cos(o)*T+L.y,ellipticalArcStartAngle:A,ellipticalArcEndAngle:A+b,ellipticalArcAngle:P,ellipticalArcCenter:L,resultantRx:n,resultantRy:e}},y=function(t,n){t=t||500;for(var e,i=0,r=[],h=[],s=n(0),a=0;a0?Math.sqrt(l*l+c):0,y=u*u+c>0?Math.sqrt(u*u+c):0,p=u+Math.sqrt(u*u+c)!==0&&(l+f)/(u+y)!=0?c*Math.log(Math.abs((l+f)/(u+y))):0;return Math.sqrt(a)/2*(l*f-u*y+p)},_=function(t,n,e){return{x:2*(1-e)*(t[1]-t[0])+2*e*(t[2]-t[1]),y:2*(1-e)*(n[1]-n[0])+2*e*(n[2]-n[1])}};function S(t,n,e){var i=N(1,e,t),r=N(1,e,n),h=i*i+r*r;return Math.sqrt(h)}var N=function t(n,e,i){var r,h,s=i.length-1;if(0===s)return 0;if(0===n){h=0;for(var a=0;a<=s;a++)h+=A[s][a]*Math.pow(1-e,s-a)*Math.pow(e,a)*i[a];return h}r=new Array(s);for(var o=0;o.001;){var a=e(r+h),o=Math.abs(t-a)/n;if(o500)break}return r},j=e((function(t,n,e,r,h,s,a,o){var g=this;i(this,"a",void 0),i(this,"b",void 0),i(this,"c",void 0),i(this,"d",void 0),i(this,"length",void 0),i(this,"getArcLength",void 0),i(this,"getPoint",void 0),i(this,"getDerivative",void 0),i(this,"getTotalLength",(function(){return g.length})),i(this,"getPointAtLength",(function(t){var n=[g.a.x,g.b.x,g.c.x,g.d.x],e=[g.a.y,g.b.y,g.c.y,g.d.y],i=C(t,g.length,(function(t){return g.getArcLength(n,e,t)}));return g.getPoint(n,e,i)})),i(this,"getTangentAtLength",(function(t){var n=[g.a.x,g.b.x,g.c.x,g.d.x],e=[g.a.y,g.b.y,g.c.y,g.d.y],i=C(t,g.length,(function(t){return g.getArcLength(n,e,t)})),r=g.getDerivative(n,e,i),h=Math.sqrt(r.x*r.x+r.y*r.y);return h>0?{x:r.x/h,y:r.y/h}:{x:0,y:0}})),i(this,"getPropertiesAtLength",(function(t){var n,e=[g.a.x,g.b.x,g.c.x,g.d.x],i=[g.a.y,g.b.y,g.c.y,g.d.y],r=C(t,g.length,(function(t){return g.getArcLength(e,i,t)})),h=g.getDerivative(e,i,r),s=Math.sqrt(h.x*h.x+h.y*h.y);n=s>0?{x:h.x/s,y:h.y/s}:{x:0,y:0};var a=g.getPoint(e,i,r);return{x:a.x,y:a.y,tangentX:n.x,tangentY:n.y}})),i(this,"getC",(function(){return g.c})),i(this,"getD",(function(){return g.d})),this.a={x:t,y:n},this.b={x:e,y:r},this.c={x:h,y:s},void 0!==a&&void 0!==o?(this.getArcLength=m,this.getPoint=b,this.getDerivative=P,this.d={x:a,y:o}):(this.getArcLength=q,this.getPoint=T,this.getDerivative=_,this.d={x:0,y:0}),this.length=this.getArcLength([this.a.x,this.b.x,this.c.x,this.d.x],[this.a.y,this.b.y,this.c.y,this.d.y],1)})),O=e((function(t){var n=this;i(this,"length",0),i(this,"partial_lengths",[]),i(this,"functions",[]),i(this,"initial_point",null),i(this,"getPartAtLength",(function(t){t<0?t=0:t>n.length&&(t=n.length);for(var e=n.partial_lengths.length-1;n.partial_lengths[e]>=t&&e>0;)e--;return e++,{fraction:t-n.partial_lengths[e-1],i:e}})),i(this,"getTotalLength",(function(){return n.length})),i(this,"getPointAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getPointAtLength(e.fraction);if(n.initial_point)return n.initial_point;throw new Error("Wrong function at this part.")})),i(this,"getTangentAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getTangentAtLength(e.fraction);if(n.initial_point)return{x:0,y:0};throw new Error("Wrong function at this part.")})),i(this,"getPropertiesAtLength",(function(t){var e=n.getPartAtLength(t),i=n.functions[e.i];if(i)return i.getPropertiesAtLength(e.fraction);if(n.initial_point)return{x:n.initial_point.x,y:n.initial_point.y,tangentX:0,tangentY:0};throw new Error("Wrong function at this part.")})),i(this,"getParts",(function(){for(var t=[],e=0;e0?t:"M0,0").match(o);if(!n)throw new Error("No path elements found in string ".concat(t));return n.reduce((function(t,n){var e=n.charAt(0),i=e.toLowerCase(),h=u(n.substring(1));if("m"===i&&h.length>2&&(t.push([e].concat(r(h.splice(0,2)))),i="l",e="m"===e?"l":"L"),"a"===i.toLowerCase()&&(5===h.length||6===h.length)){var s=n.substring(1).trim().split(" ");h=[Number(s[0]),Number(s[1]),Number(s[2]),Number(s[3].charAt(0)),Number(s[3].charAt(1)),Number(s[3].substring(2)),Number(s[4])]}for(;h.length>=0;){if(h.length===a[i]){t.push([e].concat(r(h.splice(0,a[i]))));break}if(h.length0?(this.length+=e.getTotalLength(),this.functions.push(e),s=[h[y][5]+s[0],h[y][6]+s[1]]):this.functions.push(new l(s[0],s[0],s[1],s[1]));else if("S"===h[y][0]){if(y>0&&["C","c","S","s"].indexOf(h[y-1][0])>-1){if(e){var p=e.getC();e=new j(s[0],s[1],2*s[0]-p.x,2*s[1]-p.y,h[y][1],h[y][2],h[y][3],h[y][4])}}else e=new j(s[0],s[1],s[0],s[1],h[y][1],h[y][2],h[y][3],h[y][4]);e&&(this.length+=e.getTotalLength(),s=[h[y][3],h[y][4]],this.functions.push(e))}else if("s"===h[y][0]){if(y>0&&["C","c","S","s"].indexOf(h[y-1][0])>-1){if(e){var x=e.getC(),v=e.getD();e=new j(s[0],s[1],s[0]+v.x-x.x,s[1]+v.y-x.y,s[0]+h[y][1],s[1]+h[y][2],s[0]+h[y][3],s[1]+h[y][4])}}else e=new j(s[0],s[1],s[0],s[1],s[0]+h[y][1],s[1]+h[y][2],s[0]+h[y][3],s[1]+h[y][4]);e&&(this.length+=e.getTotalLength(),s=[h[y][3]+s[0],h[y][4]+s[1]],this.functions.push(e))}else if("Q"===h[y][0]){if(s[0]==h[y][1]&&s[1]==h[y][2]){var M=new l(h[y][1],h[y][3],h[y][2],h[y][4]);this.length+=M.getTotalLength(),this.functions.push(M)}else e=new j(s[0],s[1],h[y][1],h[y][2],h[y][3],h[y][4],void 0,void 0),this.length+=e.getTotalLength(),this.functions.push(e);s=[h[y][3],h[y][4]],g=[h[y][1],h[y][2]]}else if("q"===h[y][0]){if(0!=h[y][1]||0!=h[y][2])e=new j(s[0],s[1],s[0]+h[y][1],s[1]+h[y][2],s[0]+h[y][3],s[1]+h[y][4],void 0,void 0),this.length+=e.getTotalLength(),this.functions.push(e);else{var w=new l(s[0]+h[y][1],s[0]+h[y][3],s[1]+h[y][2],s[1]+h[y][4]);this.length+=w.getTotalLength(),this.functions.push(w)}g=[s[0]+h[y][1],s[1]+h[y][2]],s=[h[y][3]+s[0],h[y][4]+s[1]]}else if("T"===h[y][0]){if(y>0&&["Q","q","T","t"].indexOf(h[y-1][0])>-1)e=new j(s[0],s[1],2*s[0]-g[0],2*s[1]-g[1],h[y][1],h[y][2],void 0,void 0),this.functions.push(e),this.length+=e.getTotalLength();else{var L=new l(s[0],h[y][1],s[1],h[y][2]);this.functions.push(L),this.length+=L.getTotalLength()}g=[2*s[0]-g[0],2*s[1]-g[1]],s=[h[y][1],h[y][2]]}else if("t"===h[y][0]){if(y>0&&["Q","q","T","t"].indexOf(h[y-1][0])>-1)e=new j(s[0],s[1],2*s[0]-g[0],2*s[1]-g[1],s[0]+h[y][1],s[1]+h[y][2],void 0,void 0),this.length+=e.getTotalLength(),this.functions.push(e);else{var d=new l(s[0],s[0]+h[y][1],s[1],s[1]+h[y][2]);this.length+=d.getTotalLength(),this.functions.push(d)}g=[2*s[0]-g[0],2*s[1]-g[1]],s=[h[y][1]+s[0],h[y][2]+s[1]]}else if("A"===h[y][0]){var A=new c(s[0],s[1],h[y][1],h[y][2],h[y][3],1===h[y][4],1===h[y][5],h[y][6],h[y][7]);this.length+=A.getTotalLength(),s=[h[y][6],h[y][7]],this.functions.push(A)}else if("a"===h[y][0]){var b=new c(s[0],s[1],h[y][1],h[y][2],h[y][3],1===h[y][4],1===h[y][5],s[0]+h[y][6],s[1]+h[y][7]);this.length+=b.getTotalLength(),s=[s[0]+h[y][6],s[1]+h[y][7]],this.functions.push(b)}this.partial_lengths.push(this.length)}})),E=e((function(t){var n=this;if(i(this,"inst",void 0),i(this,"getTotalLength",(function(){return n.inst.getTotalLength()})),i(this,"getPointAtLength",(function(t){return n.inst.getPointAtLength(t)})),i(this,"getTangentAtLength",(function(t){return n.inst.getTangentAtLength(t)})),i(this,"getPropertiesAtLength",(function(t){return n.inst.getPropertiesAtLength(t)})),i(this,"getParts",(function(){return n.inst.getParts()})),this.inst=new O(t),!(this instanceof E))return new E(t)}));t.svgPathProperties=E}));
3 |
--------------------------------------------------------------------------------
/dist/types/arc.d.ts:
--------------------------------------------------------------------------------
1 | import { Properties, Point, PointProperties } from "./types";
2 | export declare class Arc implements Properties {
3 | private x0;
4 | private y0;
5 | private rx;
6 | private ry;
7 | private xAxisRotate;
8 | private LargeArcFlag;
9 | private SweepFlag;
10 | private x1;
11 | private y1;
12 | private length;
13 | constructor(x0: number, y0: number, rx: number, ry: number, xAxisRotate: number, LargeArcFlag: boolean, SweepFlag: boolean, x1: number, y1: number);
14 | getTotalLength: () => number;
15 | getPointAtLength: (fractionLength: number) => Point;
16 | getTangentAtLength: (fractionLength: number) => Point;
17 | getPropertiesAtLength: (fractionLength: number) => PointProperties;
18 | }
19 |
--------------------------------------------------------------------------------
/dist/types/bezier-functions.d.ts:
--------------------------------------------------------------------------------
1 | import { Point } from "./types";
2 | export declare const cubicPoint: (xs: number[], ys: number[], t: number) => Point;
3 | export declare const cubicDerivative: (xs: number[], ys: number[], t: number) => Point;
4 | export declare const getCubicArcLength: (xs: number[], ys: number[], t: number) => number;
5 | export declare const quadraticPoint: (xs: number[], ys: number[], t: number) => Point;
6 | export declare const getQuadraticArcLength: (xs: number[], ys: number[], t: number) => number;
7 | export declare const quadraticDerivative: (xs: number[], ys: number[], t: number) => {
8 | x: number;
9 | y: number;
10 | };
11 | export declare const t2length: (length: number, totalLength: number, func: (t: number) => number) => number;
12 |
--------------------------------------------------------------------------------
/dist/types/bezier-values.d.ts:
--------------------------------------------------------------------------------
1 | export declare const tValues: number[][];
2 | export declare const cValues: number[][];
3 | export declare const binomialCoefficients: number[][];
4 |
--------------------------------------------------------------------------------
/dist/types/bezier.d.ts:
--------------------------------------------------------------------------------
1 | import { Properties, Point } from "./types";
2 | export declare class Bezier implements Properties {
3 | private a;
4 | private b;
5 | private c;
6 | private d;
7 | private length;
8 | private getArcLength;
9 | private getPoint;
10 | private getDerivative;
11 | constructor(ax: number, ay: number, bx: number, by: number, cx: number, cy: number, dx: number | undefined, dy: number | undefined);
12 | getTotalLength: () => number;
13 | getPointAtLength: (length: number) => Point;
14 | getTangentAtLength: (length: number) => Point;
15 | getPropertiesAtLength: (length: number) => {
16 | x: number;
17 | y: number;
18 | tangentX: number;
19 | tangentY: number;
20 | };
21 | getC: () => Point;
22 | getD: () => Point;
23 | }
24 |
--------------------------------------------------------------------------------
/dist/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import SVGPathProperties from "./svg-path-properties";
2 | declare class _svgPathProperties {
3 | inst: SVGPathProperties;
4 | constructor(svgPath: string);
5 | getTotalLength: () => number;
6 | getPointAtLength: (fractionLength: number) => import("./types").Point;
7 | getTangentAtLength: (fractionLength: number) => import("./types").Point;
8 | getPropertiesAtLength: (fractionLength: number) => import("./types").PointProperties;
9 | getParts: () => import("./types").PartProperties[];
10 | }
11 | type svgPathProperties = _svgPathProperties;
12 | export declare const svgPathProperties: typeof _svgPathProperties & ((val: string) => svgPathProperties);
13 | export {};
14 |
--------------------------------------------------------------------------------
/dist/types/linear.d.ts:
--------------------------------------------------------------------------------
1 | import { Properties, Point, PointProperties } from "./types";
2 | export declare class LinearPosition implements Properties {
3 | private x0;
4 | private x1;
5 | private y0;
6 | private y1;
7 | constructor(x0: number, x1: number, y0: number, y1: number);
8 | getTotalLength: () => number;
9 | getPointAtLength: (pos: number) => Point;
10 | getTangentAtLength: (_: number) => Point;
11 | getPropertiesAtLength: (pos: number) => PointProperties;
12 | }
13 |
--------------------------------------------------------------------------------
/dist/types/parse.d.ts:
--------------------------------------------------------------------------------
1 | declare const _default: (path: string) => [string, ...number[]][];
2 | export default _default;
3 |
--------------------------------------------------------------------------------
/dist/types/svg-path-properties.d.ts:
--------------------------------------------------------------------------------
1 | import { Properties, PartProperties, Point } from "./types";
2 | export default class SVGPathProperties implements Properties {
3 | private length;
4 | private partial_lengths;
5 | private functions;
6 | private initial_point;
7 | constructor(source: string | [string, ...Array][]);
8 | private getPartAtLength;
9 | getTotalLength: () => number;
10 | getPointAtLength: (fractionLength: number) => Point;
11 | getTangentAtLength: (fractionLength: number) => Point;
12 | getPropertiesAtLength: (fractionLength: number) => import("./types").PointProperties;
13 | getParts: () => PartProperties[];
14 | }
15 |
--------------------------------------------------------------------------------
/dist/types/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface Properties {
2 | getTotalLength(): number;
3 | getPointAtLength(pos: number): Point;
4 | getTangentAtLength(pos: number): Point;
5 | getPropertiesAtLength(pos: number): PointProperties;
6 | }
7 | export interface PartProperties {
8 | start: Point;
9 | end: Point;
10 | length: number;
11 | getPointAtLength(pos: number): Point;
12 | getTangentAtLength(pos: number): Point;
13 | getPropertiesAtLength(pos: number): PointProperties;
14 | }
15 | export interface Point {
16 | x: number;
17 | y: number;
18 | }
19 | export type PointArray = [number, number];
20 | export interface PointProperties {
21 | x: number;
22 | y: number;
23 | tangentX: number;
24 | tangentY: number;
25 | }
26 | export type pathOrders = "a" | "c" | "h" | "l" | "m" | "q" | "s" | "t" | "v" | "z";
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svg-path-properties",
3 | "version": "1.3.0",
4 | "description": "Calculate the length for an SVG path, to use it with node or a Canvas element",
5 | "keywords": [
6 | "path",
7 | "getPointAtLength",
8 | "length",
9 | "canvas",
10 | "svg"
11 | ],
12 | "homepage": "http://geoexamples.com/path-properties/",
13 | "main": "dist/svg-path-properties.cjs.js",
14 | "module": "dist/svg-path-properties.esm.js",
15 | "unpkg": "dist/svg-path-properties.min.js",
16 | "types": "dist/types/index.d.ts",
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/rveciana/svg-path-properties.git"
20 | },
21 | "scripts": {
22 | "type-check": "tsc --noEmit",
23 | "type-check:watch": "npm run type-check -- --watch",
24 | "build:types": "tsc --emitDeclarationOnly",
25 | "build:js": "rollup -c --bundleConfigAsCjs",
26 | "build": "npm run build:types && npm run build:js",
27 | "test": "ts-node --skip-project node_modules/tape/bin/tape test/**/*-test.ts",
28 | "preversion": "npm run build && git add dist && git commit --allow-empty -am \"new version\"",
29 | "postpublish": "git push && git push --tags"
30 | },
31 | "author": {
32 | "name": "Roger Veciana i Rovira",
33 | "url": "https://geoexamples.com"
34 | },
35 | "license": "ISC",
36 | "devDependencies": {
37 | "@babel/core": "^7.23.5",
38 | "@babel/plugin-proposal-class-properties": "^7.18.6",
39 | "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
40 | "@babel/preset-env": "^7.23.5",
41 | "@babel/preset-typescript": "^7.23.3",
42 | "@types/tape": "^5.6.4",
43 | "babel6-plugin-strip-class-callcheck": "^6.0.0",
44 | "eslint": "^8.55.0",
45 | "prettier": "^3.1.0",
46 | "rollup": "^4.6.1",
47 | "rollup-plugin-babel": "^4.4.0",
48 | "rollup-plugin-commonjs": "^10.1.0",
49 | "rollup-plugin-node-resolve": "^5.2.0",
50 | "rollup-plugin-terser": "^7.0.2",
51 | "tape": "^5.7.2",
52 | "ts-node": "^10.9.1",
53 | "typescript": "^5.3.2"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from "rollup-plugin-commonjs";
2 | import resolve from "rollup-plugin-node-resolve";
3 | import babel from "rollup-plugin-babel";
4 | import { terser } from "rollup-plugin-terser";
5 |
6 | import pkg from "./package.json";
7 | const extensions = [".js", ".jsx", ".ts", ".tsx"];
8 |
9 | const name = "svgPathProperties";
10 | const banner = `// ${pkg.homepage} v${
11 | pkg.version
12 | } Copyright ${new Date().getFullYear()} ${pkg.author.name}`;
13 |
14 | export default {
15 | input: "./src/index.ts",
16 |
17 | // Specify here external modules which you don't want to include in your bundle (for instance: 'lodash', 'moment' etc.)
18 | // https://rollupjs.org/guide/en#external-e-external
19 | external: [],
20 |
21 | plugins: [
22 | // Allows node_modules resolution
23 | resolve({ extensions }),
24 |
25 | // Allow bundling cjs modules. Rollup doesn't understand cjs
26 | commonjs(),
27 |
28 | // Compile TypeScript/JavaScript files
29 | babel({ extensions, include: ["src/**/*"] }),
30 | terser({
31 | output: {
32 | preamble: banner
33 | }
34 | })
35 | ],
36 |
37 | output: [
38 | {
39 | file: pkg.main,
40 | format: "cjs"
41 | },
42 | {
43 | file: pkg.module,
44 | format: "es"
45 | },
46 | {
47 | file: pkg.unpkg,
48 | format: "umd",
49 | name,
50 |
51 | // https://rollupjs.org/guide/en#output-globals-g-globals
52 | globals: {}
53 | }
54 | ]
55 | };
56 |
--------------------------------------------------------------------------------
/src/arc.ts:
--------------------------------------------------------------------------------
1 | import { Properties, Point, PointProperties } from "./types";
2 |
3 | export class Arc implements Properties {
4 | private x0: number;
5 | private y0: number;
6 | private rx: number;
7 | private ry: number;
8 | private xAxisRotate: number;
9 | private LargeArcFlag: boolean;
10 | private SweepFlag: boolean;
11 | private x1: number;
12 | private y1: number;
13 | private length: number;
14 | constructor(
15 | x0: number,
16 | y0: number,
17 | rx: number,
18 | ry: number,
19 | xAxisRotate: number,
20 | LargeArcFlag: boolean,
21 | SweepFlag: boolean,
22 | x1: number,
23 | y1: number
24 | ) {
25 | this.x0 = x0;
26 | this.y0 = y0;
27 | this.rx = rx;
28 | this.ry = ry;
29 | this.xAxisRotate = xAxisRotate;
30 | this.LargeArcFlag = LargeArcFlag;
31 | this.SweepFlag = SweepFlag;
32 | this.x1 = x1;
33 | this.y1 = y1;
34 |
35 | const lengthProperties = approximateArcLengthOfCurve(300, function(t: number) {
36 | return pointOnEllipticalArc(
37 | { x: x0, y: y0 },
38 | rx,
39 | ry,
40 | xAxisRotate,
41 | LargeArcFlag,
42 | SweepFlag,
43 | { x: x1, y: y1 },
44 | t
45 | );
46 | });
47 | this.length = lengthProperties.arcLength;
48 | }
49 |
50 | public getTotalLength = () => {
51 | return this.length;
52 | };
53 |
54 | public getPointAtLength = (fractionLength: number): Point => {
55 | if (fractionLength < 0) {
56 | fractionLength = 0;
57 | } else if (fractionLength > this.length) {
58 | fractionLength = this.length;
59 | }
60 |
61 | const position = pointOnEllipticalArc(
62 | { x: this.x0, y: this.y0 },
63 | this.rx,
64 | this.ry,
65 | this.xAxisRotate,
66 | this.LargeArcFlag,
67 | this.SweepFlag,
68 | { x: this.x1, y: this.y1 },
69 | fractionLength / this.length
70 | );
71 |
72 | return { x: position.x, y: position.y };
73 | };
74 |
75 | public getTangentAtLength = (fractionLength: number): Point => {
76 | if (fractionLength < 0) {
77 | fractionLength = 0;
78 | } else if (fractionLength > this.length) {
79 | fractionLength = this.length;
80 | }
81 | const point_dist = 0.05; // needs testing
82 | const p1 = this.getPointAtLength(fractionLength);
83 | let p2: Point;
84 |
85 | if (fractionLength < 0) {
86 | fractionLength = 0;
87 | } else if (fractionLength > this.length) {
88 | fractionLength = this.length;
89 | }
90 |
91 | if (fractionLength < this.length - point_dist) {
92 | p2 = this.getPointAtLength(fractionLength + point_dist);
93 | } else {
94 | p2 = this.getPointAtLength(fractionLength - point_dist);
95 | }
96 |
97 | const xDist = p2.x - p1.x;
98 | const yDist = p2.y - p1.y;
99 | const dist = Math.sqrt(xDist * xDist + yDist * yDist);
100 |
101 | if (fractionLength < this.length - point_dist) {
102 | return { x: -xDist / dist, y: -yDist / dist };
103 | } else {
104 | return { x: xDist / dist, y: yDist / dist };
105 | }
106 | };
107 |
108 | public getPropertiesAtLength = (fractionLength: number): PointProperties => {
109 | const tangent = this.getTangentAtLength(fractionLength);
110 | const point = this.getPointAtLength(fractionLength);
111 | return { x: point.x, y: point.y, tangentX: tangent.x, tangentY: tangent.y };
112 | };
113 | }
114 |
115 | interface PointOnEllipticalArc {
116 | x: number;
117 | y: number;
118 | ellipticalArcAngle: number;
119 | }
120 |
121 | const pointOnEllipticalArc = (
122 | p0: Point,
123 | rx: number,
124 | ry: number,
125 | xAxisRotation: number,
126 | largeArcFlag: boolean,
127 | sweepFlag: boolean,
128 | p1: Point,
129 | t: number
130 | ): PointOnEllipticalArc => {
131 | // In accordance to: http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
132 | rx = Math.abs(rx);
133 | ry = Math.abs(ry);
134 | xAxisRotation = mod(xAxisRotation, 360);
135 | const xAxisRotationRadians = toRadians(xAxisRotation);
136 | // If the endpoints are identical, then this is equivalent to omitting the elliptical arc segment entirely.
137 | if (p0.x === p1.x && p0.y === p1.y) {
138 | return { x: p0.x, y: p0.y, ellipticalArcAngle: 0 }; // Check if angle is correct
139 | }
140 |
141 | // If rx = 0 or ry = 0 then this arc is treated as a straight line segment joining the endpoints.
142 | if (rx === 0 || ry === 0) {
143 | //return this.pointOnLine(p0, p1, t);
144 | return { x: 0, y: 0, ellipticalArcAngle: 0 }; // Check if angle is correct
145 | }
146 |
147 | // Following "Conversion from endpoint to center parameterization"
148 | // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
149 |
150 | // Step #1: Compute transformedPoint
151 | const dx = (p0.x - p1.x) / 2;
152 | const dy = (p0.y - p1.y) / 2;
153 | const transformedPoint = {
154 | x: Math.cos(xAxisRotationRadians) * dx + Math.sin(xAxisRotationRadians) * dy,
155 | y: -Math.sin(xAxisRotationRadians) * dx + Math.cos(xAxisRotationRadians) * dy
156 | };
157 | // Ensure radii are large enough
158 | const radiiCheck =
159 | Math.pow(transformedPoint.x, 2) / Math.pow(rx, 2) +
160 | Math.pow(transformedPoint.y, 2) / Math.pow(ry, 2);
161 | if (radiiCheck > 1) {
162 | rx = Math.sqrt(radiiCheck) * rx;
163 | ry = Math.sqrt(radiiCheck) * ry;
164 | }
165 |
166 | // Step #2: Compute transformedCenter
167 | const cSquareNumerator =
168 | Math.pow(rx, 2) * Math.pow(ry, 2) -
169 | Math.pow(rx, 2) * Math.pow(transformedPoint.y, 2) -
170 | Math.pow(ry, 2) * Math.pow(transformedPoint.x, 2);
171 | const cSquareRootDenom =
172 | Math.pow(rx, 2) * Math.pow(transformedPoint.y, 2) +
173 | Math.pow(ry, 2) * Math.pow(transformedPoint.x, 2);
174 | let cRadicand = cSquareNumerator / cSquareRootDenom;
175 | // Make sure this never drops below zero because of precision
176 | cRadicand = cRadicand < 0 ? 0 : cRadicand;
177 | const cCoef = (largeArcFlag !== sweepFlag ? 1 : -1) * Math.sqrt(cRadicand);
178 | const transformedCenter = {
179 | x: cCoef * ((rx * transformedPoint.y) / ry),
180 | y: cCoef * (-(ry * transformedPoint.x) / rx)
181 | };
182 |
183 | // Step #3: Compute center
184 | const center = {
185 | x:
186 | Math.cos(xAxisRotationRadians) * transformedCenter.x -
187 | Math.sin(xAxisRotationRadians) * transformedCenter.y +
188 | (p0.x + p1.x) / 2,
189 | y:
190 | Math.sin(xAxisRotationRadians) * transformedCenter.x +
191 | Math.cos(xAxisRotationRadians) * transformedCenter.y +
192 | (p0.y + p1.y) / 2
193 | };
194 |
195 | // Step #4: Compute start/sweep angles
196 | // Start angle of the elliptical arc prior to the stretch and rotate operations.
197 | // Difference between the start and end angles
198 | const startVector = {
199 | x: (transformedPoint.x - transformedCenter.x) / rx,
200 | y: (transformedPoint.y - transformedCenter.y) / ry
201 | };
202 | const startAngle = angleBetween(
203 | {
204 | x: 1,
205 | y: 0
206 | },
207 | startVector
208 | );
209 |
210 | const endVector = {
211 | x: (-transformedPoint.x - transformedCenter.x) / rx,
212 | y: (-transformedPoint.y - transformedCenter.y) / ry
213 | };
214 | let sweepAngle = angleBetween(startVector, endVector);
215 |
216 | if (!sweepFlag && sweepAngle > 0) {
217 | sweepAngle -= 2 * Math.PI;
218 | } else if (sweepFlag && sweepAngle < 0) {
219 | sweepAngle += 2 * Math.PI;
220 | }
221 | // We use % instead of `mod(..)` because we want it to be -360deg to 360deg(but actually in radians)
222 | sweepAngle %= 2 * Math.PI;
223 |
224 | // From http://www.w3.org/TR/SVG/implnote.html#ArcParameterizationAlternatives
225 | const angle = startAngle + sweepAngle * t;
226 | const ellipseComponentX = rx * Math.cos(angle);
227 | const ellipseComponentY = ry * Math.sin(angle);
228 |
229 | const point = {
230 | x:
231 | Math.cos(xAxisRotationRadians) * ellipseComponentX -
232 | Math.sin(xAxisRotationRadians) * ellipseComponentY +
233 | center.x,
234 | y:
235 | Math.sin(xAxisRotationRadians) * ellipseComponentX +
236 | Math.cos(xAxisRotationRadians) * ellipseComponentY +
237 | center.y,
238 | ellipticalArcStartAngle: startAngle,
239 | ellipticalArcEndAngle: startAngle + sweepAngle,
240 | ellipticalArcAngle: angle,
241 | ellipticalArcCenter: center,
242 | resultantRx: rx,
243 | resultantRy: ry
244 | };
245 |
246 | return point;
247 | };
248 |
249 | const approximateArcLengthOfCurve = (
250 | resolution: number,
251 | pointOnCurveFunc: (t: number) => Point
252 | ) => {
253 | // Resolution is the number of segments we use
254 | resolution = resolution ? resolution : 500;
255 |
256 | let resultantArcLength = 0;
257 | const arcLengthMap = [];
258 | const approximationLines = [];
259 |
260 | let prevPoint = pointOnCurveFunc(0);
261 | let nextPoint;
262 | for (let i = 0; i < resolution; i++) {
263 | const t = clamp(i * (1 / resolution), 0, 1);
264 | nextPoint = pointOnCurveFunc(t);
265 | resultantArcLength += distance(prevPoint, nextPoint);
266 | approximationLines.push([prevPoint, nextPoint]);
267 |
268 | arcLengthMap.push({
269 | t: t,
270 | arcLength: resultantArcLength
271 | });
272 |
273 | prevPoint = nextPoint;
274 | }
275 | // Last stretch to the endpoint
276 | nextPoint = pointOnCurveFunc(1);
277 | approximationLines.push([prevPoint, nextPoint]);
278 | resultantArcLength += distance(prevPoint, nextPoint);
279 | arcLengthMap.push({
280 | t: 1,
281 | arcLength: resultantArcLength
282 | });
283 |
284 | return {
285 | arcLength: resultantArcLength,
286 | arcLengthMap: arcLengthMap,
287 | approximationLines: approximationLines
288 | };
289 | };
290 |
291 | const mod = (x: number, m: number) => {
292 | return ((x % m) + m) % m;
293 | };
294 |
295 | const toRadians = (angle: number) => {
296 | return angle * (Math.PI / 180);
297 | };
298 |
299 | const distance = (p0: Point, p1: Point) => {
300 | return Math.sqrt(Math.pow(p1.x - p0.x, 2) + Math.pow(p1.y - p0.y, 2));
301 | };
302 |
303 | const clamp = (val: number, min: number, max: number) => {
304 | return Math.min(Math.max(val, min), max);
305 | };
306 |
307 | const angleBetween = (v0: Point, v1: Point) => {
308 | const p = v0.x * v1.x + v0.y * v1.y;
309 | const n = Math.sqrt(
310 | (Math.pow(v0.x, 2) + Math.pow(v0.y, 2)) * (Math.pow(v1.x, 2) + Math.pow(v1.y, 2))
311 | );
312 | const sign = v0.x * v1.y - v0.y * v1.x < 0 ? -1 : 1;
313 | const angle = sign * Math.acos(p / n);
314 |
315 | return angle;
316 | };
317 |
--------------------------------------------------------------------------------
/src/bezier-functions.ts:
--------------------------------------------------------------------------------
1 | import { tValues, cValues, binomialCoefficients } from "./bezier-values";
2 | import { Point } from "./types";
3 | export const cubicPoint = (xs: number[], ys: number[], t: number): Point => {
4 | const x =
5 | (1 - t) * (1 - t) * (1 - t) * xs[0] +
6 | 3 * (1 - t) * (1 - t) * t * xs[1] +
7 | 3 * (1 - t) * t * t * xs[2] +
8 | t * t * t * xs[3];
9 | const y =
10 | (1 - t) * (1 - t) * (1 - t) * ys[0] +
11 | 3 * (1 - t) * (1 - t) * t * ys[1] +
12 | 3 * (1 - t) * t * t * ys[2] +
13 | t * t * t * ys[3];
14 |
15 | return { x: x, y: y };
16 | };
17 |
18 | export const cubicDerivative = (xs: number[], ys: number[], t: number) => {
19 | const derivative = quadraticPoint(
20 | [3 * (xs[1] - xs[0]), 3 * (xs[2] - xs[1]), 3 * (xs[3] - xs[2])],
21 | [3 * (ys[1] - ys[0]), 3 * (ys[2] - ys[1]), 3 * (ys[3] - ys[2])],
22 | t
23 | );
24 | return derivative;
25 | };
26 |
27 | export const getCubicArcLength = (xs: number[], ys: number[], t: number) => {
28 | let z: number;
29 | let sum: number;
30 | let correctedT: number;
31 |
32 | /*if (xs.length >= tValues.length) {
33 | throw new Error('too high n bezier');
34 | }*/
35 |
36 | const n = 20;
37 |
38 | z = t / 2;
39 | sum = 0;
40 | for (let i = 0; i < n; i++) {
41 | correctedT = z * tValues[n][i] + z;
42 | sum += cValues[n][i] * BFunc(xs, ys, correctedT);
43 | }
44 | return z * sum;
45 | };
46 |
47 | export const quadraticPoint = (
48 | xs: number[],
49 | ys: number[],
50 | t: number
51 | ): Point => {
52 | const x = (1 - t) * (1 - t) * xs[0] + 2 * (1 - t) * t * xs[1] + t * t * xs[2];
53 | const y = (1 - t) * (1 - t) * ys[0] + 2 * (1 - t) * t * ys[1] + t * t * ys[2];
54 | return { x: x, y: y };
55 | };
56 |
57 | export const getQuadraticArcLength = (
58 | xs: number[],
59 | ys: number[],
60 | t: number
61 | ) => {
62 | if (t === undefined) {
63 | t = 1;
64 | }
65 | const ax = xs[0] - 2 * xs[1] + xs[2];
66 | const ay = ys[0] - 2 * ys[1] + ys[2];
67 | const bx = 2 * xs[1] - 2 * xs[0];
68 | const by = 2 * ys[1] - 2 * ys[0];
69 |
70 | const A = 4 * (ax * ax + ay * ay);
71 | const B = 4 * (ax * bx + ay * by);
72 | const C = bx * bx + by * by;
73 |
74 | if (A === 0) {
75 | return (
76 | t * Math.sqrt(Math.pow(xs[2] - xs[0], 2) + Math.pow(ys[2] - ys[0], 2))
77 | );
78 | }
79 | const b = B / (2 * A);
80 | const c = C / A;
81 | const u = t + b;
82 | const k = c - b * b;
83 |
84 | const uuk = u * u + k > 0 ? Math.sqrt(u * u + k) : 0;
85 | const bbk = b * b + k > 0 ? Math.sqrt(b * b + k) : 0;
86 | const term =
87 | b + Math.sqrt(b * b + k) !== 0 && ((u + uuk) / (b + bbk)) != 0
88 | ? k * Math.log(Math.abs((u + uuk) / (b + bbk)))
89 | : 0;
90 |
91 | return (Math.sqrt(A) / 2) * (u * uuk - b * bbk + term);
92 | };
93 |
94 | export const quadraticDerivative = (xs: number[], ys: number[], t: number) => {
95 | return {
96 | x: (1 - t) * 2 * (xs[1] - xs[0]) + t * 2 * (xs[2] - xs[1]),
97 | y: (1 - t) * 2 * (ys[1] - ys[0]) + t * 2 * (ys[2] - ys[1]),
98 | };
99 | };
100 |
101 | function BFunc(xs: number[], ys: number[], t: number) {
102 | const xbase = getDerivative(1, t, xs);
103 | const ybase = getDerivative(1, t, ys);
104 | const combined = xbase * xbase + ybase * ybase;
105 | return Math.sqrt(combined);
106 | }
107 |
108 | /**
109 | * Compute the curve derivative (hodograph) at t.
110 | */
111 | const getDerivative = (derivative: number, t: number, vs: number[]): number => {
112 | // the derivative of any 't'-less function is zero.
113 | const n = vs.length - 1;
114 | let _vs;
115 | let value;
116 |
117 | if (n === 0) {
118 | return 0;
119 | }
120 |
121 | // direct values? compute!
122 | if (derivative === 0) {
123 | value = 0;
124 | for (let k = 0; k <= n; k++) {
125 | value +=
126 | binomialCoefficients[n][k] *
127 | Math.pow(1 - t, n - k) *
128 | Math.pow(t, k) *
129 | vs[k];
130 | }
131 | return value;
132 | } else {
133 | // Still some derivative? go down one order, then try
134 | // for the lower order curve's.
135 | _vs = new Array(n);
136 | for (let k = 0; k < n; k++) {
137 | _vs[k] = n * (vs[k + 1] - vs[k]);
138 | }
139 | return getDerivative(derivative - 1, t, _vs);
140 | }
141 | };
142 |
143 | export const t2length = (
144 | length: number,
145 | totalLength: number,
146 | func: (t: number) => number
147 | ): number => {
148 | let error = 1;
149 | let t = length / totalLength;
150 | let step = (length - func(t)) / totalLength;
151 |
152 | let numIterations = 0;
153 | while (error > 0.001) {
154 | const increasedTLength = func(t + step);
155 | const increasedTError = Math.abs(length - increasedTLength) / totalLength;
156 | if (increasedTError < error) {
157 | error = increasedTError;
158 | t += step;
159 | } else {
160 | const decreasedTLength = func(t - step);
161 | const decreasedTError = Math.abs(length - decreasedTLength) / totalLength;
162 | if (decreasedTError < error) {
163 | error = decreasedTError;
164 | t -= step;
165 | } else {
166 | step /= 2;
167 | }
168 | }
169 |
170 | numIterations++;
171 | if (numIterations > 500) {
172 | break;
173 | }
174 | }
175 |
176 | return t;
177 | };
178 |
--------------------------------------------------------------------------------
/src/bezier-values.ts:
--------------------------------------------------------------------------------
1 | // Legendre-Gauss abscissae (xi values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x))
2 | export const tValues = [
3 | [],
4 | [],
5 | [
6 | -0.5773502691896257645091487805019574556476,
7 | 0.5773502691896257645091487805019574556476
8 | ],
9 | [
10 | 0,
11 | -0.7745966692414833770358530799564799221665,
12 | 0.7745966692414833770358530799564799221665
13 | ],
14 | [
15 | -0.3399810435848562648026657591032446872005,
16 | 0.3399810435848562648026657591032446872005,
17 | -0.8611363115940525752239464888928095050957,
18 | 0.8611363115940525752239464888928095050957
19 | ],
20 | [
21 | 0,
22 | -0.5384693101056830910363144207002088049672,
23 | 0.5384693101056830910363144207002088049672,
24 | -0.9061798459386639927976268782993929651256,
25 | 0.9061798459386639927976268782993929651256
26 | ],
27 | [
28 | 0.6612093864662645136613995950199053470064,
29 | -0.6612093864662645136613995950199053470064,
30 | -0.2386191860831969086305017216807119354186,
31 | 0.2386191860831969086305017216807119354186,
32 | -0.9324695142031520278123015544939946091347,
33 | 0.9324695142031520278123015544939946091347
34 | ],
35 | [
36 | 0,
37 | 0.4058451513773971669066064120769614633473,
38 | -0.4058451513773971669066064120769614633473,
39 | -0.7415311855993944398638647732807884070741,
40 | 0.7415311855993944398638647732807884070741,
41 | -0.9491079123427585245261896840478512624007,
42 | 0.9491079123427585245261896840478512624007
43 | ],
44 | [
45 | -0.1834346424956498049394761423601839806667,
46 | 0.1834346424956498049394761423601839806667,
47 | -0.5255324099163289858177390491892463490419,
48 | 0.5255324099163289858177390491892463490419,
49 | -0.7966664774136267395915539364758304368371,
50 | 0.7966664774136267395915539364758304368371,
51 | -0.9602898564975362316835608685694729904282,
52 | 0.9602898564975362316835608685694729904282
53 | ],
54 | [
55 | 0,
56 | -0.8360311073266357942994297880697348765441,
57 | 0.8360311073266357942994297880697348765441,
58 | -0.9681602395076260898355762029036728700494,
59 | 0.9681602395076260898355762029036728700494,
60 | -0.3242534234038089290385380146433366085719,
61 | 0.3242534234038089290385380146433366085719,
62 | -0.6133714327005903973087020393414741847857,
63 | 0.6133714327005903973087020393414741847857
64 | ],
65 | [
66 | -0.1488743389816312108848260011297199846175,
67 | 0.1488743389816312108848260011297199846175,
68 | -0.4333953941292471907992659431657841622,
69 | 0.4333953941292471907992659431657841622,
70 | -0.6794095682990244062343273651148735757692,
71 | 0.6794095682990244062343273651148735757692,
72 | -0.8650633666889845107320966884234930485275,
73 | 0.8650633666889845107320966884234930485275,
74 | -0.9739065285171717200779640120844520534282,
75 | 0.9739065285171717200779640120844520534282
76 | ],
77 | [
78 | 0,
79 | -0.2695431559523449723315319854008615246796,
80 | 0.2695431559523449723315319854008615246796,
81 | -0.5190961292068118159257256694586095544802,
82 | 0.5190961292068118159257256694586095544802,
83 | -0.7301520055740493240934162520311534580496,
84 | 0.7301520055740493240934162520311534580496,
85 | -0.8870625997680952990751577693039272666316,
86 | 0.8870625997680952990751577693039272666316,
87 | -0.9782286581460569928039380011228573907714,
88 | 0.9782286581460569928039380011228573907714
89 | ],
90 | [
91 | -0.1252334085114689154724413694638531299833,
92 | 0.1252334085114689154724413694638531299833,
93 | -0.3678314989981801937526915366437175612563,
94 | 0.3678314989981801937526915366437175612563,
95 | -0.587317954286617447296702418940534280369,
96 | 0.587317954286617447296702418940534280369,
97 | -0.7699026741943046870368938332128180759849,
98 | 0.7699026741943046870368938332128180759849,
99 | -0.9041172563704748566784658661190961925375,
100 | 0.9041172563704748566784658661190961925375,
101 | -0.9815606342467192506905490901492808229601,
102 | 0.9815606342467192506905490901492808229601
103 | ],
104 | [
105 | 0,
106 | -0.2304583159551347940655281210979888352115,
107 | 0.2304583159551347940655281210979888352115,
108 | -0.4484927510364468528779128521276398678019,
109 | 0.4484927510364468528779128521276398678019,
110 | -0.6423493394403402206439846069955156500716,
111 | 0.6423493394403402206439846069955156500716,
112 | -0.8015780907333099127942064895828598903056,
113 | 0.8015780907333099127942064895828598903056,
114 | -0.9175983992229779652065478365007195123904,
115 | 0.9175983992229779652065478365007195123904,
116 | -0.9841830547185881494728294488071096110649,
117 | 0.9841830547185881494728294488071096110649
118 | ],
119 | [
120 | -0.1080549487073436620662446502198347476119,
121 | 0.1080549487073436620662446502198347476119,
122 | -0.3191123689278897604356718241684754668342,
123 | 0.3191123689278897604356718241684754668342,
124 | -0.5152486363581540919652907185511886623088,
125 | 0.5152486363581540919652907185511886623088,
126 | -0.6872929048116854701480198030193341375384,
127 | 0.6872929048116854701480198030193341375384,
128 | -0.8272013150697649931897947426503949610397,
129 | 0.8272013150697649931897947426503949610397,
130 | -0.928434883663573517336391139377874264477,
131 | 0.928434883663573517336391139377874264477,
132 | -0.986283808696812338841597266704052801676,
133 | 0.986283808696812338841597266704052801676
134 | ],
135 | [
136 | 0,
137 | -0.2011940939974345223006283033945962078128,
138 | 0.2011940939974345223006283033945962078128,
139 | -0.3941513470775633698972073709810454683627,
140 | 0.3941513470775633698972073709810454683627,
141 | -0.5709721726085388475372267372539106412383,
142 | 0.5709721726085388475372267372539106412383,
143 | -0.7244177313601700474161860546139380096308,
144 | 0.7244177313601700474161860546139380096308,
145 | -0.8482065834104272162006483207742168513662,
146 | 0.8482065834104272162006483207742168513662,
147 | -0.9372733924007059043077589477102094712439,
148 | 0.9372733924007059043077589477102094712439,
149 | -0.9879925180204854284895657185866125811469,
150 | 0.9879925180204854284895657185866125811469
151 | ],
152 | [
153 | -0.0950125098376374401853193354249580631303,
154 | 0.0950125098376374401853193354249580631303,
155 | -0.281603550779258913230460501460496106486,
156 | 0.281603550779258913230460501460496106486,
157 | -0.45801677765722738634241944298357757354,
158 | 0.45801677765722738634241944298357757354,
159 | -0.6178762444026437484466717640487910189918,
160 | 0.6178762444026437484466717640487910189918,
161 | -0.7554044083550030338951011948474422683538,
162 | 0.7554044083550030338951011948474422683538,
163 | -0.8656312023878317438804678977123931323873,
164 | 0.8656312023878317438804678977123931323873,
165 | -0.9445750230732325760779884155346083450911,
166 | 0.9445750230732325760779884155346083450911,
167 | -0.9894009349916499325961541734503326274262,
168 | 0.9894009349916499325961541734503326274262
169 | ],
170 | [
171 | 0,
172 | -0.1784841814958478558506774936540655574754,
173 | 0.1784841814958478558506774936540655574754,
174 | -0.3512317634538763152971855170953460050405,
175 | 0.3512317634538763152971855170953460050405,
176 | -0.5126905370864769678862465686295518745829,
177 | 0.5126905370864769678862465686295518745829,
178 | -0.6576711592166907658503022166430023351478,
179 | 0.6576711592166907658503022166430023351478,
180 | -0.7815140038968014069252300555204760502239,
181 | 0.7815140038968014069252300555204760502239,
182 | -0.8802391537269859021229556944881556926234,
183 | 0.8802391537269859021229556944881556926234,
184 | -0.9506755217687677612227169578958030214433,
185 | 0.9506755217687677612227169578958030214433,
186 | -0.9905754753144173356754340199406652765077,
187 | 0.9905754753144173356754340199406652765077
188 | ],
189 | [
190 | -0.0847750130417353012422618529357838117333,
191 | 0.0847750130417353012422618529357838117333,
192 | -0.2518862256915055095889728548779112301628,
193 | 0.2518862256915055095889728548779112301628,
194 | -0.4117511614628426460359317938330516370789,
195 | 0.4117511614628426460359317938330516370789,
196 | -0.5597708310739475346078715485253291369276,
197 | 0.5597708310739475346078715485253291369276,
198 | -0.6916870430603532078748910812888483894522,
199 | 0.6916870430603532078748910812888483894522,
200 | -0.8037049589725231156824174550145907971032,
201 | 0.8037049589725231156824174550145907971032,
202 | -0.8926024664975557392060605911271455154078,
203 | 0.8926024664975557392060605911271455154078,
204 | -0.9558239495713977551811958929297763099728,
205 | 0.9558239495713977551811958929297763099728,
206 | -0.9915651684209309467300160047061507702525,
207 | 0.9915651684209309467300160047061507702525
208 | ],
209 | [
210 | 0,
211 | -0.1603586456402253758680961157407435495048,
212 | 0.1603586456402253758680961157407435495048,
213 | -0.3165640999636298319901173288498449178922,
214 | 0.3165640999636298319901173288498449178922,
215 | -0.4645707413759609457172671481041023679762,
216 | 0.4645707413759609457172671481041023679762,
217 | -0.6005453046616810234696381649462392798683,
218 | 0.6005453046616810234696381649462392798683,
219 | -0.7209661773352293786170958608237816296571,
220 | 0.7209661773352293786170958608237816296571,
221 | -0.8227146565371428249789224867127139017745,
222 | 0.8227146565371428249789224867127139017745,
223 | -0.9031559036148179016426609285323124878093,
224 | 0.9031559036148179016426609285323124878093,
225 | -0.960208152134830030852778840687651526615,
226 | 0.960208152134830030852778840687651526615,
227 | -0.9924068438435844031890176702532604935893,
228 | 0.9924068438435844031890176702532604935893
229 | ],
230 | [
231 | -0.0765265211334973337546404093988382110047,
232 | 0.0765265211334973337546404093988382110047,
233 | -0.227785851141645078080496195368574624743,
234 | 0.227785851141645078080496195368574624743,
235 | -0.3737060887154195606725481770249272373957,
236 | 0.3737060887154195606725481770249272373957,
237 | -0.5108670019508270980043640509552509984254,
238 | 0.5108670019508270980043640509552509984254,
239 | -0.6360536807265150254528366962262859367433,
240 | 0.6360536807265150254528366962262859367433,
241 | -0.7463319064601507926143050703556415903107,
242 | 0.7463319064601507926143050703556415903107,
243 | -0.8391169718222188233945290617015206853296,
244 | 0.8391169718222188233945290617015206853296,
245 | -0.9122344282513259058677524412032981130491,
246 | 0.9122344282513259058677524412032981130491,
247 | -0.963971927277913791267666131197277221912,
248 | 0.963971927277913791267666131197277221912,
249 | -0.9931285991850949247861223884713202782226,
250 | 0.9931285991850949247861223884713202782226
251 | ],
252 | [
253 | 0,
254 | -0.1455618541608950909370309823386863301163,
255 | 0.1455618541608950909370309823386863301163,
256 | -0.288021316802401096600792516064600319909,
257 | 0.288021316802401096600792516064600319909,
258 | -0.4243421202074387835736688885437880520964,
259 | 0.4243421202074387835736688885437880520964,
260 | -0.551618835887219807059018796724313286622,
261 | 0.551618835887219807059018796724313286622,
262 | -0.667138804197412319305966669990339162597,
263 | 0.667138804197412319305966669990339162597,
264 | -0.7684399634756779086158778513062280348209,
265 | 0.7684399634756779086158778513062280348209,
266 | -0.8533633645833172836472506385875676702761,
267 | 0.8533633645833172836472506385875676702761,
268 | -0.9200993341504008287901871337149688941591,
269 | 0.9200993341504008287901871337149688941591,
270 | -0.9672268385663062943166222149076951614246,
271 | 0.9672268385663062943166222149076951614246,
272 | -0.9937521706203895002602420359379409291933,
273 | 0.9937521706203895002602420359379409291933
274 | ],
275 | [
276 | -0.0697392733197222212138417961186280818222,
277 | 0.0697392733197222212138417961186280818222,
278 | -0.2078604266882212854788465339195457342156,
279 | 0.2078604266882212854788465339195457342156,
280 | -0.3419358208920842251581474204273796195591,
281 | 0.3419358208920842251581474204273796195591,
282 | -0.4693558379867570264063307109664063460953,
283 | 0.4693558379867570264063307109664063460953,
284 | -0.5876404035069115929588769276386473488776,
285 | 0.5876404035069115929588769276386473488776,
286 | -0.6944872631866827800506898357622567712673,
287 | 0.6944872631866827800506898357622567712673,
288 | -0.7878168059792081620042779554083515213881,
289 | 0.7878168059792081620042779554083515213881,
290 | -0.8658125777203001365364256370193787290847,
291 | 0.8658125777203001365364256370193787290847,
292 | -0.9269567721871740005206929392590531966353,
293 | 0.9269567721871740005206929392590531966353,
294 | -0.9700604978354287271239509867652687108059,
295 | 0.9700604978354287271239509867652687108059,
296 | -0.994294585482399292073031421161298980393,
297 | 0.994294585482399292073031421161298980393
298 | ],
299 | [
300 | 0,
301 | -0.1332568242984661109317426822417661370104,
302 | 0.1332568242984661109317426822417661370104,
303 | -0.264135680970344930533869538283309602979,
304 | 0.264135680970344930533869538283309602979,
305 | -0.390301038030290831421488872880605458578,
306 | 0.390301038030290831421488872880605458578,
307 | -0.5095014778460075496897930478668464305448,
308 | 0.5095014778460075496897930478668464305448,
309 | -0.6196098757636461563850973116495956533871,
310 | 0.6196098757636461563850973116495956533871,
311 | -0.7186613631319501944616244837486188483299,
312 | 0.7186613631319501944616244837486188483299,
313 | -0.8048884016188398921511184069967785579414,
314 | 0.8048884016188398921511184069967785579414,
315 | -0.8767523582704416673781568859341456716389,
316 | 0.8767523582704416673781568859341456716389,
317 | -0.9329710868260161023491969890384229782357,
318 | 0.9329710868260161023491969890384229782357,
319 | -0.9725424712181152319560240768207773751816,
320 | 0.9725424712181152319560240768207773751816,
321 | -0.9947693349975521235239257154455743605736,
322 | 0.9947693349975521235239257154455743605736
323 | ],
324 | [
325 | -0.0640568928626056260850430826247450385909,
326 | 0.0640568928626056260850430826247450385909,
327 | -0.1911188674736163091586398207570696318404,
328 | 0.1911188674736163091586398207570696318404,
329 | -0.3150426796961633743867932913198102407864,
330 | 0.3150426796961633743867932913198102407864,
331 | -0.4337935076260451384870842319133497124524,
332 | 0.4337935076260451384870842319133497124524,
333 | -0.5454214713888395356583756172183723700107,
334 | 0.5454214713888395356583756172183723700107,
335 | -0.6480936519369755692524957869107476266696,
336 | 0.6480936519369755692524957869107476266696,
337 | -0.7401241915785543642438281030999784255232,
338 | 0.7401241915785543642438281030999784255232,
339 | -0.8200019859739029219539498726697452080761,
340 | 0.8200019859739029219539498726697452080761,
341 | -0.8864155270044010342131543419821967550873,
342 | 0.8864155270044010342131543419821967550873,
343 | -0.9382745520027327585236490017087214496548,
344 | 0.9382745520027327585236490017087214496548,
345 | -0.9747285559713094981983919930081690617411,
346 | 0.9747285559713094981983919930081690617411,
347 | -0.9951872199970213601799974097007368118745,
348 | 0.9951872199970213601799974097007368118745
349 | ]
350 | ];
351 |
352 | // Legendre-Gauss weights (wi values, defined by a function linked to in the Bezier primer article)
353 | export const cValues = [
354 | [],
355 | [],
356 | [1.0, 1.0],
357 | [
358 | 0.8888888888888888888888888888888888888888,
359 | 0.5555555555555555555555555555555555555555,
360 | 0.5555555555555555555555555555555555555555
361 | ],
362 | [
363 | 0.6521451548625461426269360507780005927646,
364 | 0.6521451548625461426269360507780005927646,
365 | 0.3478548451374538573730639492219994072353,
366 | 0.3478548451374538573730639492219994072353
367 | ],
368 | [
369 | 0.5688888888888888888888888888888888888888,
370 | 0.4786286704993664680412915148356381929122,
371 | 0.4786286704993664680412915148356381929122,
372 | 0.2369268850561890875142640407199173626432,
373 | 0.2369268850561890875142640407199173626432
374 | ],
375 | [
376 | 0.3607615730481386075698335138377161116615,
377 | 0.3607615730481386075698335138377161116615,
378 | 0.4679139345726910473898703439895509948116,
379 | 0.4679139345726910473898703439895509948116,
380 | 0.1713244923791703450402961421727328935268,
381 | 0.1713244923791703450402961421727328935268
382 | ],
383 | [
384 | 0.4179591836734693877551020408163265306122,
385 | 0.3818300505051189449503697754889751338783,
386 | 0.3818300505051189449503697754889751338783,
387 | 0.2797053914892766679014677714237795824869,
388 | 0.2797053914892766679014677714237795824869,
389 | 0.1294849661688696932706114326790820183285,
390 | 0.1294849661688696932706114326790820183285
391 | ],
392 | [
393 | 0.3626837833783619829651504492771956121941,
394 | 0.3626837833783619829651504492771956121941,
395 | 0.3137066458778872873379622019866013132603,
396 | 0.3137066458778872873379622019866013132603,
397 | 0.2223810344533744705443559944262408844301,
398 | 0.2223810344533744705443559944262408844301,
399 | 0.1012285362903762591525313543099621901153,
400 | 0.1012285362903762591525313543099621901153
401 | ],
402 | [
403 | 0.3302393550012597631645250692869740488788,
404 | 0.1806481606948574040584720312429128095143,
405 | 0.1806481606948574040584720312429128095143,
406 | 0.0812743883615744119718921581105236506756,
407 | 0.0812743883615744119718921581105236506756,
408 | 0.3123470770400028400686304065844436655987,
409 | 0.3123470770400028400686304065844436655987,
410 | 0.2606106964029354623187428694186328497718,
411 | 0.2606106964029354623187428694186328497718
412 | ],
413 | [
414 | 0.295524224714752870173892994651338329421,
415 | 0.295524224714752870173892994651338329421,
416 | 0.2692667193099963550912269215694693528597,
417 | 0.2692667193099963550912269215694693528597,
418 | 0.2190863625159820439955349342281631924587,
419 | 0.2190863625159820439955349342281631924587,
420 | 0.1494513491505805931457763396576973324025,
421 | 0.1494513491505805931457763396576973324025,
422 | 0.0666713443086881375935688098933317928578,
423 | 0.0666713443086881375935688098933317928578
424 | ],
425 | [
426 | 0.272925086777900630714483528336342189156,
427 | 0.2628045445102466621806888698905091953727,
428 | 0.2628045445102466621806888698905091953727,
429 | 0.2331937645919904799185237048431751394317,
430 | 0.2331937645919904799185237048431751394317,
431 | 0.1862902109277342514260976414316558916912,
432 | 0.1862902109277342514260976414316558916912,
433 | 0.1255803694649046246346942992239401001976,
434 | 0.1255803694649046246346942992239401001976,
435 | 0.0556685671161736664827537204425485787285,
436 | 0.0556685671161736664827537204425485787285
437 | ],
438 | [
439 | 0.2491470458134027850005624360429512108304,
440 | 0.2491470458134027850005624360429512108304,
441 | 0.2334925365383548087608498989248780562594,
442 | 0.2334925365383548087608498989248780562594,
443 | 0.2031674267230659217490644558097983765065,
444 | 0.2031674267230659217490644558097983765065,
445 | 0.160078328543346226334652529543359071872,
446 | 0.160078328543346226334652529543359071872,
447 | 0.1069393259953184309602547181939962242145,
448 | 0.1069393259953184309602547181939962242145,
449 | 0.047175336386511827194615961485017060317,
450 | 0.047175336386511827194615961485017060317
451 | ],
452 | [
453 | 0.2325515532308739101945895152688359481566,
454 | 0.2262831802628972384120901860397766184347,
455 | 0.2262831802628972384120901860397766184347,
456 | 0.2078160475368885023125232193060527633865,
457 | 0.2078160475368885023125232193060527633865,
458 | 0.1781459807619457382800466919960979955128,
459 | 0.1781459807619457382800466919960979955128,
460 | 0.1388735102197872384636017768688714676218,
461 | 0.1388735102197872384636017768688714676218,
462 | 0.0921214998377284479144217759537971209236,
463 | 0.0921214998377284479144217759537971209236,
464 | 0.0404840047653158795200215922009860600419,
465 | 0.0404840047653158795200215922009860600419
466 | ],
467 | [
468 | 0.2152638534631577901958764433162600352749,
469 | 0.2152638534631577901958764433162600352749,
470 | 0.2051984637212956039659240656612180557103,
471 | 0.2051984637212956039659240656612180557103,
472 | 0.1855383974779378137417165901251570362489,
473 | 0.1855383974779378137417165901251570362489,
474 | 0.1572031671581935345696019386238421566056,
475 | 0.1572031671581935345696019386238421566056,
476 | 0.1215185706879031846894148090724766259566,
477 | 0.1215185706879031846894148090724766259566,
478 | 0.0801580871597602098056332770628543095836,
479 | 0.0801580871597602098056332770628543095836,
480 | 0.0351194603317518630318328761381917806197,
481 | 0.0351194603317518630318328761381917806197
482 | ],
483 | [
484 | 0.2025782419255612728806201999675193148386,
485 | 0.1984314853271115764561183264438393248186,
486 | 0.1984314853271115764561183264438393248186,
487 | 0.1861610000155622110268005618664228245062,
488 | 0.1861610000155622110268005618664228245062,
489 | 0.1662692058169939335532008604812088111309,
490 | 0.1662692058169939335532008604812088111309,
491 | 0.1395706779261543144478047945110283225208,
492 | 0.1395706779261543144478047945110283225208,
493 | 0.1071592204671719350118695466858693034155,
494 | 0.1071592204671719350118695466858693034155,
495 | 0.0703660474881081247092674164506673384667,
496 | 0.0703660474881081247092674164506673384667,
497 | 0.0307532419961172683546283935772044177217,
498 | 0.0307532419961172683546283935772044177217
499 | ],
500 | [
501 | 0.1894506104550684962853967232082831051469,
502 | 0.1894506104550684962853967232082831051469,
503 | 0.1826034150449235888667636679692199393835,
504 | 0.1826034150449235888667636679692199393835,
505 | 0.1691565193950025381893120790303599622116,
506 | 0.1691565193950025381893120790303599622116,
507 | 0.1495959888165767320815017305474785489704,
508 | 0.1495959888165767320815017305474785489704,
509 | 0.1246289712555338720524762821920164201448,
510 | 0.1246289712555338720524762821920164201448,
511 | 0.0951585116824927848099251076022462263552,
512 | 0.0951585116824927848099251076022462263552,
513 | 0.0622535239386478928628438369943776942749,
514 | 0.0622535239386478928628438369943776942749,
515 | 0.0271524594117540948517805724560181035122,
516 | 0.0271524594117540948517805724560181035122
517 | ],
518 | [
519 | 0.1794464703562065254582656442618856214487,
520 | 0.1765627053669926463252709901131972391509,
521 | 0.1765627053669926463252709901131972391509,
522 | 0.1680041021564500445099706637883231550211,
523 | 0.1680041021564500445099706637883231550211,
524 | 0.1540457610768102880814315948019586119404,
525 | 0.1540457610768102880814315948019586119404,
526 | 0.1351363684685254732863199817023501973721,
527 | 0.1351363684685254732863199817023501973721,
528 | 0.1118838471934039710947883856263559267358,
529 | 0.1118838471934039710947883856263559267358,
530 | 0.0850361483171791808835353701910620738504,
531 | 0.0850361483171791808835353701910620738504,
532 | 0.0554595293739872011294401653582446605128,
533 | 0.0554595293739872011294401653582446605128,
534 | 0.0241483028685479319601100262875653246916,
535 | 0.0241483028685479319601100262875653246916
536 | ],
537 | [
538 | 0.1691423829631435918406564701349866103341,
539 | 0.1691423829631435918406564701349866103341,
540 | 0.1642764837458327229860537764659275904123,
541 | 0.1642764837458327229860537764659275904123,
542 | 0.1546846751262652449254180038363747721932,
543 | 0.1546846751262652449254180038363747721932,
544 | 0.1406429146706506512047313037519472280955,
545 | 0.1406429146706506512047313037519472280955,
546 | 0.1225552067114784601845191268002015552281,
547 | 0.1225552067114784601845191268002015552281,
548 | 0.1009420441062871655628139849248346070628,
549 | 0.1009420441062871655628139849248346070628,
550 | 0.0764257302548890565291296776166365256053,
551 | 0.0764257302548890565291296776166365256053,
552 | 0.0497145488949697964533349462026386416808,
553 | 0.0497145488949697964533349462026386416808,
554 | 0.0216160135264833103133427102664524693876,
555 | 0.0216160135264833103133427102664524693876
556 | ],
557 | [
558 | 0.1610544498487836959791636253209167350399,
559 | 0.1589688433939543476499564394650472016787,
560 | 0.1589688433939543476499564394650472016787,
561 | 0.152766042065859666778855400897662998461,
562 | 0.152766042065859666778855400897662998461,
563 | 0.1426067021736066117757461094419029724756,
564 | 0.1426067021736066117757461094419029724756,
565 | 0.1287539625393362276755157848568771170558,
566 | 0.1287539625393362276755157848568771170558,
567 | 0.1115666455473339947160239016817659974813,
568 | 0.1115666455473339947160239016817659974813,
569 | 0.0914900216224499994644620941238396526609,
570 | 0.0914900216224499994644620941238396526609,
571 | 0.0690445427376412265807082580060130449618,
572 | 0.0690445427376412265807082580060130449618,
573 | 0.0448142267656996003328381574019942119517,
574 | 0.0448142267656996003328381574019942119517,
575 | 0.0194617882297264770363120414644384357529,
576 | 0.0194617882297264770363120414644384357529
577 | ],
578 | [
579 | 0.1527533871307258506980843319550975934919,
580 | 0.1527533871307258506980843319550975934919,
581 | 0.1491729864726037467878287370019694366926,
582 | 0.1491729864726037467878287370019694366926,
583 | 0.1420961093183820513292983250671649330345,
584 | 0.1420961093183820513292983250671649330345,
585 | 0.1316886384491766268984944997481631349161,
586 | 0.1316886384491766268984944997481631349161,
587 | 0.118194531961518417312377377711382287005,
588 | 0.118194531961518417312377377711382287005,
589 | 0.1019301198172404350367501354803498761666,
590 | 0.1019301198172404350367501354803498761666,
591 | 0.0832767415767047487247581432220462061001,
592 | 0.0832767415767047487247581432220462061001,
593 | 0.0626720483341090635695065351870416063516,
594 | 0.0626720483341090635695065351870416063516,
595 | 0.040601429800386941331039952274932109879,
596 | 0.040601429800386941331039952274932109879,
597 | 0.0176140071391521183118619623518528163621,
598 | 0.0176140071391521183118619623518528163621
599 | ],
600 | [
601 | 0.1460811336496904271919851476833711882448,
602 | 0.1445244039899700590638271665537525436099,
603 | 0.1445244039899700590638271665537525436099,
604 | 0.1398873947910731547221334238675831108927,
605 | 0.1398873947910731547221334238675831108927,
606 | 0.132268938633337461781052574496775604329,
607 | 0.132268938633337461781052574496775604329,
608 | 0.1218314160537285341953671771257335983563,
609 | 0.1218314160537285341953671771257335983563,
610 | 0.1087972991671483776634745780701056420336,
611 | 0.1087972991671483776634745780701056420336,
612 | 0.0934444234560338615532897411139320884835,
613 | 0.0934444234560338615532897411139320884835,
614 | 0.0761001136283793020170516533001831792261,
615 | 0.0761001136283793020170516533001831792261,
616 | 0.0571344254268572082836358264724479574912,
617 | 0.0571344254268572082836358264724479574912,
618 | 0.0369537897708524937999506682993296661889,
619 | 0.0369537897708524937999506682993296661889,
620 | 0.0160172282577743333242246168584710152658,
621 | 0.0160172282577743333242246168584710152658
622 | ],
623 | [
624 | 0.1392518728556319933754102483418099578739,
625 | 0.1392518728556319933754102483418099578739,
626 | 0.1365414983460151713525738312315173965863,
627 | 0.1365414983460151713525738312315173965863,
628 | 0.1311735047870623707329649925303074458757,
629 | 0.1311735047870623707329649925303074458757,
630 | 0.1232523768105124242855609861548144719594,
631 | 0.1232523768105124242855609861548144719594,
632 | 0.1129322960805392183934006074217843191142,
633 | 0.1129322960805392183934006074217843191142,
634 | 0.1004141444428809649320788378305362823508,
635 | 0.1004141444428809649320788378305362823508,
636 | 0.0859416062170677274144436813727028661891,
637 | 0.0859416062170677274144436813727028661891,
638 | 0.0697964684245204880949614189302176573987,
639 | 0.0697964684245204880949614189302176573987,
640 | 0.0522933351526832859403120512732112561121,
641 | 0.0522933351526832859403120512732112561121,
642 | 0.0337749015848141547933022468659129013491,
643 | 0.0337749015848141547933022468659129013491,
644 | 0.0146279952982722006849910980471854451902,
645 | 0.0146279952982722006849910980471854451902
646 | ],
647 | [
648 | 0.1336545721861061753514571105458443385831,
649 | 0.132462039404696617371642464703316925805,
650 | 0.132462039404696617371642464703316925805,
651 | 0.1289057221880821499785953393997936532597,
652 | 0.1289057221880821499785953393997936532597,
653 | 0.1230490843067295304675784006720096548158,
654 | 0.1230490843067295304675784006720096548158,
655 | 0.1149966402224113649416435129339613014914,
656 | 0.1149966402224113649416435129339613014914,
657 | 0.1048920914645414100740861850147438548584,
658 | 0.1048920914645414100740861850147438548584,
659 | 0.0929157660600351474770186173697646486034,
660 | 0.0929157660600351474770186173697646486034,
661 | 0.0792814117767189549228925247420432269137,
662 | 0.0792814117767189549228925247420432269137,
663 | 0.0642324214085258521271696151589109980391,
664 | 0.0642324214085258521271696151589109980391,
665 | 0.0480376717310846685716410716320339965612,
666 | 0.0480376717310846685716410716320339965612,
667 | 0.0309880058569794443106942196418845053837,
668 | 0.0309880058569794443106942196418845053837,
669 | 0.0134118594871417720813094934586150649766,
670 | 0.0134118594871417720813094934586150649766
671 | ],
672 | [
673 | 0.1279381953467521569740561652246953718517,
674 | 0.1279381953467521569740561652246953718517,
675 | 0.1258374563468282961213753825111836887264,
676 | 0.1258374563468282961213753825111836887264,
677 | 0.121670472927803391204463153476262425607,
678 | 0.121670472927803391204463153476262425607,
679 | 0.1155056680537256013533444839067835598622,
680 | 0.1155056680537256013533444839067835598622,
681 | 0.1074442701159656347825773424466062227946,
682 | 0.1074442701159656347825773424466062227946,
683 | 0.0976186521041138882698806644642471544279,
684 | 0.0976186521041138882698806644642471544279,
685 | 0.086190161531953275917185202983742667185,
686 | 0.086190161531953275917185202983742667185,
687 | 0.0733464814110803057340336152531165181193,
688 | 0.0733464814110803057340336152531165181193,
689 | 0.0592985849154367807463677585001085845412,
690 | 0.0592985849154367807463677585001085845412,
691 | 0.0442774388174198061686027482113382288593,
692 | 0.0442774388174198061686027482113382288593,
693 | 0.0285313886289336631813078159518782864491,
694 | 0.0285313886289336631813078159518782864491,
695 | 0.0123412297999871995468056670700372915759,
696 | 0.0123412297999871995468056670700372915759
697 | ]
698 | ];
699 |
700 | // LUT for binomial coefficient arrays per curve order 'n'
701 | export const binomialCoefficients = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]];
702 |
--------------------------------------------------------------------------------
/src/bezier.ts:
--------------------------------------------------------------------------------
1 | import { Properties, Point } from "./types";
2 |
3 | import {
4 | cubicPoint,
5 | getCubicArcLength,
6 | cubicDerivative,
7 | getQuadraticArcLength,
8 | quadraticPoint,
9 | quadraticDerivative,
10 | t2length
11 | } from "./bezier-functions";
12 |
13 | export class Bezier implements Properties {
14 | private a: Point;
15 | private b: Point;
16 | private c: Point;
17 | private d: Point;
18 | private length: number;
19 | private getArcLength: (xs: number[], ys: number[], t: number) => number;
20 | private getPoint: (xs: number[], ys: number[], t: number) => Point;
21 | private getDerivative: (xs: number[], ys: number[], t: number) => Point;
22 | constructor(
23 | ax: number,
24 | ay: number,
25 | bx: number,
26 | by: number,
27 | cx: number,
28 | cy: number,
29 | dx: number | undefined,
30 | dy: number | undefined
31 | ) {
32 | this.a = { x: ax, y: ay };
33 | this.b = { x: bx, y: by };
34 | this.c = { x: cx, y: cy };
35 |
36 | if (dx !== undefined && dy !== undefined) {
37 | this.getArcLength = getCubicArcLength;
38 | this.getPoint = cubicPoint;
39 | this.getDerivative = cubicDerivative;
40 | this.d = { x: dx, y: dy };
41 | } else {
42 | this.getArcLength = getQuadraticArcLength;
43 | this.getPoint = quadraticPoint;
44 | this.getDerivative = quadraticDerivative;
45 | this.d = { x: 0, y: 0 };
46 | }
47 | this.length = this.getArcLength(
48 | [this.a.x, this.b.x, this.c.x, this.d.x],
49 | [this.a.y, this.b.y, this.c.y, this.d.y],
50 | 1
51 | );
52 | }
53 | public getTotalLength = () => {
54 | return this.length;
55 | };
56 | public getPointAtLength = (length: number) => {
57 | const xs = [this.a.x, this.b.x, this.c.x, this.d.x];
58 | const xy = [this.a.y, this.b.y, this.c.y, this.d.y];
59 | const t = t2length(length, this.length, i => this.getArcLength(xs, xy, i));
60 |
61 | return this.getPoint(xs, xy, t);
62 | };
63 | public getTangentAtLength = (length: number) => {
64 | const xs = [this.a.x, this.b.x, this.c.x, this.d.x];
65 | const xy = [this.a.y, this.b.y, this.c.y, this.d.y];
66 | const t = t2length(length, this.length, i => this.getArcLength(xs, xy, i));
67 |
68 | const derivative = this.getDerivative(xs, xy, t);
69 | const mdl = Math.sqrt(derivative.x * derivative.x + derivative.y * derivative.y);
70 | let tangent: Point;
71 | if (mdl > 0) {
72 | tangent = { x: derivative.x / mdl, y: derivative.y / mdl };
73 | } else {
74 | tangent = { x: 0, y: 0 };
75 | }
76 | return tangent;
77 | };
78 | public getPropertiesAtLength = (length: number) => {
79 | const xs = [this.a.x, this.b.x, this.c.x, this.d.x];
80 | const xy = [this.a.y, this.b.y, this.c.y, this.d.y];
81 | const t = t2length(length, this.length, i => this.getArcLength(xs, xy, i));
82 |
83 | const derivative = this.getDerivative(xs, xy, t);
84 | const mdl = Math.sqrt(derivative.x * derivative.x + derivative.y * derivative.y);
85 | let tangent: Point;
86 | if (mdl > 0) {
87 | tangent = { x: derivative.x / mdl, y: derivative.y / mdl };
88 | } else {
89 | tangent = { x: 0, y: 0 };
90 | }
91 | const point = this.getPoint(xs, xy, t);
92 | return { x: point.x, y: point.y, tangentX: tangent.x, tangentY: tangent.y };
93 | };
94 |
95 | public getC = () => {
96 | return this.c;
97 | };
98 | public getD = () => {
99 | return this.d;
100 | };
101 | }
102 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import SVGPathProperties from "./svg-path-properties";
2 |
3 | //https://stackoverflow.com/a/48362715/1086633
4 | class _svgPathProperties {
5 | inst: SVGPathProperties;
6 |
7 | constructor(svgPath: string) {
8 | this.inst = new SVGPathProperties(svgPath);
9 | if (!(this instanceof svgPathProperties)) {
10 | return new svgPathProperties(svgPath);
11 | }
12 | }
13 |
14 | public getTotalLength = () => this.inst.getTotalLength();
15 | public getPointAtLength = (fractionLength: number) =>
16 | this.inst.getPointAtLength(fractionLength);
17 | public getTangentAtLength = (fractionLength: number) =>
18 | this.inst.getTangentAtLength(fractionLength);
19 | public getPropertiesAtLength = (fractionLength: number) =>
20 | this.inst.getPropertiesAtLength(fractionLength);
21 | public getParts = () => this.inst.getParts();
22 | }
23 | type svgPathProperties = _svgPathProperties;
24 | export const svgPathProperties = _svgPathProperties as typeof _svgPathProperties &
25 | ((val: string) => svgPathProperties);
26 |
--------------------------------------------------------------------------------
/src/linear.ts:
--------------------------------------------------------------------------------
1 | import { Properties, Point, PointProperties } from "./types";
2 |
3 | export class LinearPosition implements Properties {
4 | private x0: number;
5 | private x1: number;
6 | private y0: number;
7 | private y1: number;
8 |
9 | constructor(x0: number, x1: number, y0: number, y1: number) {
10 | this.x0 = x0;
11 | this.x1 = x1;
12 | this.y0 = y0;
13 | this.y1 = y1;
14 | }
15 |
16 | public getTotalLength = () => {
17 | return Math.sqrt(
18 | Math.pow(this.x0 - this.x1, 2) + Math.pow(this.y0 - this.y1, 2)
19 | );
20 | };
21 |
22 | public getPointAtLength = (pos: number): Point => {
23 | let fraction =
24 | pos /
25 | Math.sqrt(
26 | Math.pow(this.x0 - this.x1, 2) + Math.pow(this.y0 - this.y1, 2)
27 | );
28 |
29 | fraction = Number.isNaN(fraction) ? 1 : fraction;
30 | const newDeltaX = (this.x1 - this.x0) * fraction;
31 | const newDeltaY = (this.y1 - this.y0) * fraction;
32 |
33 | return { x: this.x0 + newDeltaX, y: this.y0 + newDeltaY };
34 | };
35 |
36 | public getTangentAtLength = (_: number): Point => {
37 | const module = Math.sqrt(
38 | (this.x1 - this.x0) * (this.x1 - this.x0) +
39 | (this.y1 - this.y0) * (this.y1 - this.y0)
40 | );
41 | return {
42 | x: (this.x1 - this.x0) / module,
43 | y: (this.y1 - this.y0) / module,
44 | };
45 | };
46 |
47 | public getPropertiesAtLength = (pos: number): PointProperties => {
48 | const point = this.getPointAtLength(pos);
49 | const tangent = this.getTangentAtLength(pos);
50 | return { x: point.x, y: point.y, tangentX: tangent.x, tangentY: tangent.y };
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { pathOrders } from "./types";
2 |
3 | const length: { [key in pathOrders]: number } = {
4 | a: 7,
5 | c: 6,
6 | h: 1,
7 | l: 2,
8 | m: 2,
9 | q: 4,
10 | s: 4,
11 | t: 2,
12 | v: 1,
13 | z: 0
14 | };
15 | const segmentRegExp = /([astvzqmhlc])([^astvzqmhlc]*)/gi;
16 | const numberRegExp = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi;
17 |
18 | export default (path: string) => {
19 | const segments = (path && path.length > 0 ? path : "M0,0").match(segmentRegExp);
20 | if (!segments) {
21 | throw new Error(`No path elements found in string ${path}`);
22 | }
23 | return segments.reduce((segmentsArray: [string, ...Array][], segmentString: string) => {
24 | let command = segmentString.charAt(0);
25 | let type: pathOrders = command.toLowerCase() as pathOrders;
26 | let args = parseValues(segmentString.substring(1));
27 |
28 | // overloaded moveTo
29 | if (type === "m" && args.length > 2) {
30 | segmentsArray.push([command, ...args.splice(0, 2)]);
31 | type = "l";
32 | command = command === "m" ? "l" : "L";
33 | }
34 |
35 | // overloaded arcTo
36 | if (type.toLowerCase() === "a" && (args.length === 5 || args.length === 6)) {
37 | const aArgs = segmentString.substring(1).trim().split(' ');
38 | args = [
39 | Number(aArgs[0]),
40 | Number(aArgs[1]),
41 | Number(aArgs[2]),
42 | Number(aArgs[3].charAt(0)),
43 | Number(aArgs[3].charAt(1)),
44 | Number(aArgs[3].substring(2)),
45 | Number(aArgs[4]),
46 | ];
47 | }
48 |
49 | while (args.length >= 0) {
50 | if (args.length === length[type]) {
51 | segmentsArray.push([command, ...args.splice(0, length[type])]);
52 | break;
53 | }
54 | if (args.length < length[type]) {
55 | throw new Error(
56 | `Malformed path data: "${command}" must have ${length[type]} elements and has ${args.length}: ${segmentString}`
57 | );
58 | }
59 | segmentsArray.push([command, ...args.splice(0, length[type])]);
60 | }
61 |
62 | return segmentsArray;
63 | }, []);
64 | };
65 |
66 | const parseValues = (args: string) => {
67 | const numbers = args.match(numberRegExp);
68 | return numbers ? numbers.map(Number) : [];
69 | };
70 |
--------------------------------------------------------------------------------
/src/svg-path-properties.ts:
--------------------------------------------------------------------------------
1 | import parse from "./parse";
2 | import { PointArray, Properties, PartProperties, Point } from "./types";
3 | import { LinearPosition } from "./linear";
4 | import { Arc } from "./arc";
5 | import { Bezier } from "./bezier";
6 |
7 | export default class SVGPathProperties implements Properties {
8 | private length: number = 0;
9 | private partial_lengths: number[] = [];
10 | private functions: (null | Properties)[] = [];
11 | private initial_point: null | Point = null;
12 | constructor(source: string | [string, ...Array][]) {
13 | const parsed = Array.isArray(source) ? source : parse(source);
14 | let cur: PointArray = [0, 0];
15 | let prev_point: PointArray = [0, 0];
16 | let curve: Bezier | undefined;
17 | let ringStart: PointArray = [0, 0];
18 | for (let i = 0; i < parsed.length; i++) {
19 | //moveTo
20 | if (parsed[i][0] === "M") {
21 | cur = [parsed[i][1], parsed[i][2]];
22 | ringStart = [cur[0], cur[1]];
23 | this.functions.push(null);
24 | if (i === 0) {
25 | this.initial_point = { x: parsed[i][1], y: parsed[i][2] };
26 | }
27 | } else if (parsed[i][0] === "m") {
28 | cur = [parsed[i][1] + cur[0], parsed[i][2] + cur[1]];
29 | ringStart = [cur[0], cur[1]];
30 | this.functions.push(null);
31 | //lineTo
32 | } else if (parsed[i][0] === "L") {
33 | this.length += Math.sqrt(
34 | Math.pow(cur[0] - parsed[i][1], 2) +
35 | Math.pow(cur[1] - parsed[i][2], 2)
36 | );
37 | this.functions.push(
38 | new LinearPosition(cur[0], parsed[i][1], cur[1], parsed[i][2])
39 | );
40 | cur = [parsed[i][1], parsed[i][2]];
41 | } else if (parsed[i][0] === "l") {
42 | this.length += Math.sqrt(
43 | Math.pow(parsed[i][1], 2) + Math.pow(parsed[i][2], 2)
44 | );
45 | this.functions.push(
46 | new LinearPosition(
47 | cur[0],
48 | parsed[i][1] + cur[0],
49 | cur[1],
50 | parsed[i][2] + cur[1]
51 | )
52 | );
53 | cur = [parsed[i][1] + cur[0], parsed[i][2] + cur[1]];
54 | } else if (parsed[i][0] === "H") {
55 | this.length += Math.abs(cur[0] - parsed[i][1]);
56 | this.functions.push(
57 | new LinearPosition(cur[0], parsed[i][1], cur[1], cur[1])
58 | );
59 | cur[0] = parsed[i][1];
60 | } else if (parsed[i][0] === "h") {
61 | this.length += Math.abs(parsed[i][1]);
62 | this.functions.push(
63 | new LinearPosition(cur[0], cur[0] + parsed[i][1], cur[1], cur[1])
64 | );
65 | cur[0] = parsed[i][1] + cur[0];
66 | } else if (parsed[i][0] === "V") {
67 | this.length += Math.abs(cur[1] - parsed[i][1]);
68 | this.functions.push(
69 | new LinearPosition(cur[0], cur[0], cur[1], parsed[i][1])
70 | );
71 | cur[1] = parsed[i][1];
72 | } else if (parsed[i][0] === "v") {
73 | this.length += Math.abs(parsed[i][1]);
74 | this.functions.push(
75 | new LinearPosition(cur[0], cur[0], cur[1], cur[1] + parsed[i][1])
76 | );
77 | cur[1] = parsed[i][1] + cur[1];
78 | //Close path
79 | } else if (parsed[i][0] === "z" || parsed[i][0] === "Z") {
80 | this.length += Math.sqrt(
81 | Math.pow(ringStart[0] - cur[0], 2) +
82 | Math.pow(ringStart[1] - cur[1], 2)
83 | );
84 | this.functions.push(
85 | new LinearPosition(cur[0], ringStart[0], cur[1], ringStart[1])
86 | );
87 | cur = [ringStart[0], ringStart[1]];
88 | //Cubic Bezier curves
89 | } else if (parsed[i][0] === "C") {
90 | curve = new Bezier(
91 | cur[0],
92 | cur[1],
93 | parsed[i][1],
94 | parsed[i][2],
95 | parsed[i][3],
96 | parsed[i][4],
97 | parsed[i][5],
98 | parsed[i][6]
99 | );
100 | this.length += curve.getTotalLength();
101 | cur = [parsed[i][5], parsed[i][6]];
102 | this.functions.push(curve);
103 | } else if (parsed[i][0] === "c") {
104 | curve = new Bezier(
105 | cur[0],
106 | cur[1],
107 | cur[0] + parsed[i][1],
108 | cur[1] + parsed[i][2],
109 | cur[0] + parsed[i][3],
110 | cur[1] + parsed[i][4],
111 | cur[0] + parsed[i][5],
112 | cur[1] + parsed[i][6]
113 | );
114 | if (curve.getTotalLength() > 0) {
115 | this.length += curve.getTotalLength();
116 | this.functions.push(curve);
117 | cur = [parsed[i][5] + cur[0], parsed[i][6] + cur[1]];
118 | } else {
119 | this.functions.push(
120 | new LinearPosition(cur[0], cur[0], cur[1], cur[1])
121 | );
122 | }
123 | } else if (parsed[i][0] === "S") {
124 | if (i > 0 && ["C", "c", "S", "s"].indexOf(parsed[i - 1][0]) > -1) {
125 | if (curve) {
126 | const c = curve.getC();
127 | curve = new Bezier(
128 | cur[0],
129 | cur[1],
130 | 2 * cur[0] - c.x,
131 | 2 * cur[1] - c.y,
132 | parsed[i][1],
133 | parsed[i][2],
134 | parsed[i][3],
135 | parsed[i][4]
136 | );
137 | }
138 | } else {
139 | curve = new Bezier(
140 | cur[0],
141 | cur[1],
142 | cur[0],
143 | cur[1],
144 | parsed[i][1],
145 | parsed[i][2],
146 | parsed[i][3],
147 | parsed[i][4]
148 | );
149 | }
150 | if (curve) {
151 | this.length += curve.getTotalLength();
152 | cur = [parsed[i][3], parsed[i][4]];
153 | this.functions.push(curve);
154 | }
155 | } else if (parsed[i][0] === "s") {
156 | //240 225
157 | if (i > 0 && ["C", "c", "S", "s"].indexOf(parsed[i - 1][0]) > -1) {
158 | if (curve) {
159 | const c = curve.getC();
160 | const d = curve.getD();
161 | curve = new Bezier(
162 | cur[0],
163 | cur[1],
164 | cur[0] + d.x - c.x,
165 | cur[1] + d.y - c.y,
166 | cur[0] + parsed[i][1],
167 | cur[1] + parsed[i][2],
168 | cur[0] + parsed[i][3],
169 | cur[1] + parsed[i][4]
170 | );
171 | }
172 | } else {
173 | curve = new Bezier(
174 | cur[0],
175 | cur[1],
176 | cur[0],
177 | cur[1],
178 | cur[0] + parsed[i][1],
179 | cur[1] + parsed[i][2],
180 | cur[0] + parsed[i][3],
181 | cur[1] + parsed[i][4]
182 | );
183 | }
184 | if (curve) {
185 | this.length += curve.getTotalLength();
186 | cur = [parsed[i][3] + cur[0], parsed[i][4] + cur[1]];
187 | this.functions.push(curve);
188 | }
189 | }
190 | //Quadratic Bezier curves
191 | else if (parsed[i][0] === "Q") {
192 | if (cur[0] == parsed[i][1] && cur[1] == parsed[i][2]) {
193 | let linearCurve = new LinearPosition(
194 | parsed[i][1],
195 | parsed[i][3],
196 | parsed[i][2],
197 | parsed[i][4]
198 | );
199 | this.length += linearCurve.getTotalLength();
200 | this.functions.push(linearCurve);
201 | } else {
202 | curve = new Bezier(
203 | cur[0],
204 | cur[1],
205 | parsed[i][1],
206 | parsed[i][2],
207 | parsed[i][3],
208 | parsed[i][4],
209 | undefined,
210 | undefined
211 | );
212 | this.length += curve.getTotalLength();
213 | this.functions.push(curve);
214 | }
215 |
216 | cur = [parsed[i][3], parsed[i][4]];
217 | prev_point = [parsed[i][1], parsed[i][2]];
218 | } else if (parsed[i][0] === "q") {
219 | if (!(parsed[i][1] == 0 && parsed[i][2] == 0)) {
220 | curve = new Bezier(
221 | cur[0],
222 | cur[1],
223 | cur[0] + parsed[i][1],
224 | cur[1] + parsed[i][2],
225 | cur[0] + parsed[i][3],
226 | cur[1] + parsed[i][4],
227 | undefined,
228 | undefined
229 | );
230 | this.length += curve.getTotalLength();
231 | this.functions.push(curve);
232 | } else {
233 | let linearCurve = new LinearPosition(
234 | cur[0] + parsed[i][1],
235 | cur[0] + parsed[i][3],
236 | cur[1] + parsed[i][2],
237 | cur[1] + parsed[i][4]
238 | );
239 | this.length += linearCurve.getTotalLength();
240 | this.functions.push(linearCurve);
241 | }
242 |
243 | prev_point = [cur[0] + parsed[i][1], cur[1] + parsed[i][2]];
244 | cur = [parsed[i][3] + cur[0], parsed[i][4] + cur[1]];
245 | } else if (parsed[i][0] === "T") {
246 | if (i > 0 && ["Q", "q", "T", "t"].indexOf(parsed[i - 1][0]) > -1) {
247 | curve = new Bezier(
248 | cur[0],
249 | cur[1],
250 | 2 * cur[0] - prev_point[0],
251 | 2 * cur[1] - prev_point[1],
252 | parsed[i][1],
253 | parsed[i][2],
254 | undefined,
255 | undefined
256 | );
257 | this.functions.push(curve);
258 | this.length += curve.getTotalLength();
259 | } else {
260 | let linearCurve = new LinearPosition(
261 | cur[0],
262 | parsed[i][1],
263 | cur[1],
264 | parsed[i][2]
265 | );
266 | this.functions.push(linearCurve);
267 | this.length += linearCurve.getTotalLength();
268 | }
269 |
270 | prev_point = [2 * cur[0] - prev_point[0], 2 * cur[1] - prev_point[1]];
271 | cur = [parsed[i][1], parsed[i][2]];
272 | } else if (parsed[i][0] === "t") {
273 | if (i > 0 && ["Q", "q", "T", "t"].indexOf(parsed[i - 1][0]) > -1) {
274 | curve = new Bezier(
275 | cur[0],
276 | cur[1],
277 | 2 * cur[0] - prev_point[0],
278 | 2 * cur[1] - prev_point[1],
279 | cur[0] + parsed[i][1],
280 | cur[1] + parsed[i][2],
281 | undefined,
282 | undefined
283 | );
284 | this.length += curve.getTotalLength();
285 | this.functions.push(curve);
286 | } else {
287 | let linearCurve = new LinearPosition(
288 | cur[0],
289 | cur[0] + parsed[i][1],
290 | cur[1],
291 | cur[1] + parsed[i][2]
292 | );
293 | this.length += linearCurve.getTotalLength();
294 | this.functions.push(linearCurve);
295 | }
296 |
297 | prev_point = [2 * cur[0] - prev_point[0], 2 * cur[1] - prev_point[1]];
298 | cur = [parsed[i][1] + cur[0], parsed[i][2] + cur[1]];
299 | } else if (parsed[i][0] === "A") {
300 | const arcCurve = new Arc(
301 | cur[0],
302 | cur[1],
303 | parsed[i][1],
304 | parsed[i][2],
305 | parsed[i][3],
306 | parsed[i][4] === 1,
307 | parsed[i][5] === 1,
308 | parsed[i][6],
309 | parsed[i][7]
310 | );
311 |
312 | this.length += arcCurve.getTotalLength();
313 | cur = [parsed[i][6], parsed[i][7]];
314 | this.functions.push(arcCurve);
315 | } else if (parsed[i][0] === "a") {
316 | const arcCurve = new Arc(
317 | cur[0],
318 | cur[1],
319 | parsed[i][1],
320 | parsed[i][2],
321 | parsed[i][3],
322 | parsed[i][4] === 1,
323 | parsed[i][5] === 1,
324 | cur[0] + parsed[i][6],
325 | cur[1] + parsed[i][7]
326 | );
327 |
328 | this.length += arcCurve.getTotalLength();
329 | cur = [cur[0] + parsed[i][6], cur[1] + parsed[i][7]];
330 | this.functions.push(arcCurve);
331 | }
332 | this.partial_lengths.push(this.length);
333 | }
334 | }
335 |
336 | private getPartAtLength = (fractionLength: number) => {
337 | if (fractionLength < 0) {
338 | fractionLength = 0;
339 | } else if (fractionLength > this.length) {
340 | fractionLength = this.length;
341 | }
342 |
343 | let i = this.partial_lengths.length - 1;
344 |
345 | while (this.partial_lengths[i] >= fractionLength && i > 0) {
346 | i--;
347 | }
348 | i++;
349 | return { fraction: fractionLength - this.partial_lengths[i - 1], i: i };
350 | };
351 |
352 | public getTotalLength = () => {
353 | return this.length;
354 | };
355 |
356 | public getPointAtLength = (fractionLength: number) => {
357 | const fractionPart = this.getPartAtLength(fractionLength);
358 | const functionAtPart = this.functions[fractionPart.i];
359 |
360 | if (functionAtPart) {
361 | return functionAtPart.getPointAtLength(fractionPart.fraction);
362 | } else if (this.initial_point) {
363 | return this.initial_point;
364 | }
365 | throw new Error("Wrong function at this part.");
366 | };
367 |
368 | public getTangentAtLength = (fractionLength: number) => {
369 | const fractionPart = this.getPartAtLength(fractionLength);
370 | const functionAtPart = this.functions[fractionPart.i];
371 | if (functionAtPart) {
372 | return functionAtPart.getTangentAtLength(fractionPart.fraction);
373 | } else if (this.initial_point) {
374 | return { x: 0, y: 0 };
375 | }
376 | throw new Error("Wrong function at this part.");
377 | };
378 |
379 | public getPropertiesAtLength = (fractionLength: number) => {
380 | const fractionPart = this.getPartAtLength(fractionLength);
381 | const functionAtPart = this.functions[fractionPart.i];
382 | if (functionAtPart) {
383 | return functionAtPart.getPropertiesAtLength(fractionPart.fraction);
384 | } else if (this.initial_point) {
385 | return {
386 | x: this.initial_point.x,
387 | y: this.initial_point.y,
388 | tangentX: 0,
389 | tangentY: 0,
390 | };
391 | }
392 | throw new Error("Wrong function at this part.");
393 | };
394 |
395 | public getParts = () => {
396 | const parts: PartProperties[] = [];
397 | for (var i = 0; i < this.functions.length; i++) {
398 | if (this.functions[i] !== null) {
399 | this.functions[i] = this.functions[i] as Properties;
400 | const properties: PartProperties = {
401 | start: this.functions[i]!.getPointAtLength(0),
402 | end: this.functions[i]!.getPointAtLength(
403 | this.partial_lengths[i] - this.partial_lengths[i - 1]
404 | ),
405 | length: this.partial_lengths[i] - this.partial_lengths[i - 1],
406 | getPointAtLength: this.functions[i]!.getPointAtLength,
407 | getTangentAtLength: this.functions[i]!.getTangentAtLength,
408 | getPropertiesAtLength: this.functions[i]!.getPropertiesAtLength,
409 | };
410 | parts.push(properties);
411 | }
412 | }
413 |
414 | return parts;
415 | };
416 | }
417 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface Properties {
2 | getTotalLength(): number;
3 | getPointAtLength(pos: number): Point;
4 | getTangentAtLength(pos: number): Point;
5 | getPropertiesAtLength(pos: number): PointProperties;
6 | }
7 |
8 | export interface PartProperties {
9 | start: Point;
10 | end: Point;
11 | length: number;
12 | getPointAtLength(pos: number): Point;
13 | getTangentAtLength(pos: number): Point;
14 | getPropertiesAtLength(pos: number): PointProperties;
15 | }
16 | export interface Point {
17 | x: number;
18 | y: number;
19 | }
20 | export type PointArray = [number, number];
21 |
22 | export interface PointProperties {
23 | x: number;
24 | y: number;
25 | tangentX: number;
26 | tangentY: number;
27 | }
28 |
29 | export type pathOrders =
30 | | "a"
31 | | "c"
32 | | "h"
33 | | "l"
34 | | "m"
35 | | "q"
36 | | "s"
37 | | "t"
38 | | "v"
39 | | "z";
40 |
--------------------------------------------------------------------------------
/test/bezier-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import { Bezier } from "../src/bezier";
3 | import { inDelta } from "./inDelta";
4 |
5 | test("Testing Length Quadratic", function (test) {
6 | var curve = new Bezier(200, 300, 400, 50, 600, 300, undefined, undefined);
7 | test.true(inDelta(curve.getTotalLength(), 487.77, 0.1));
8 | test.end();
9 | });
10 | test("Testing Length Cubic", function (test) {
11 | var curve = new Bezier(200, 200, 275, 100, 575, 100, 500, 200);
12 | test.true(inDelta(curve.getTotalLength(), 383.44, 0.1));
13 | test.end();
14 | });
15 |
16 | test("Testing getPointAtLength Quadratic", function (test) {
17 | var curve = new Bezier(200, 300, 400, 50, 600, 300, undefined, undefined);
18 | var point = curve.getPointAtLength(487.77 / 6);
19 | test.true(inDelta(point.x, 255.24, 1));
20 | test.true(inDelta(point.y, 240.47, 1));
21 | test.end();
22 | });
23 |
24 | test("Testing getPointAtLength Cubic", function (test) {
25 | var curve = new Bezier(200, 200, 275, 100, 575, 100, 500, 200);
26 | var point = curve.getPointAtLength(383.44 / 6);
27 | test.true(inDelta(point.x, 249.48, 1));
28 | test.true(inDelta(point.y, 160.37, 1));
29 | test.end();
30 | });
31 |
32 | test("Testing getTangentAtLength", function (test) {
33 | var curve = new Bezier(200, 200, 275, 100, 575, 100, 500, 200);
34 |
35 | test.end();
36 | });
37 |
38 | test("Testing pull request #16 solution", function (test) {
39 | var curve = new Bezier(
40 | 640.48,
41 | 1285.21,
42 | 642.39,
43 | 644.73,
44 | 642.39,
45 | 644.73,
46 | undefined,
47 | undefined
48 | );
49 | var tangent = curve.getTangentAtLength(curve.getTotalLength() / 2);
50 | test.true(inDelta(tangent.y, 0, 1));
51 | test.true(inDelta(tangent.x, 0, 1));
52 |
53 | test.end();
54 | });
55 |
56 | test("Testing for infinite length (issue #61)", function (test) {
57 | var curve = new Bezier(267, 0, 391, 0, 512, 0, undefined, undefined);
58 | var length = curve.getTotalLength();
59 | test.true(length != Infinity);
60 |
61 | test.end();
62 | });
63 |
--------------------------------------------------------------------------------
/test/get-parts-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import SVGPathProperties from "../src/svg-path-properties";
3 |
4 | test("Testing the getParts with simple path", function (test) {
5 | var properties = new SVGPathProperties("m10,0l10,0");
6 | var parts = properties.getParts();
7 |
8 | test.equal(parts.length, 1, "A one part path must give one element array");
9 | test.equal("start" in parts[0], true, "Elements must have all properties");
10 | test.equal("end" in parts[0], true, "Elements must have all properties");
11 | test.equal("length" in parts[0], true, "Elements must have all properties");
12 | test.equal(
13 | "getPointAtLength" in parts[0],
14 | true,
15 | "Elements must have all properties"
16 | );
17 | test.equal(
18 | "getTangentAtLength" in parts[0],
19 | true,
20 | "Elements must have all properties"
21 | );
22 | test.equal(
23 | "getPropertiesAtLength" in parts[0],
24 | true,
25 | "Elements must have all properties"
26 | );
27 |
28 | properties = new SVGPathProperties("m10,0l10,0l10,0");
29 | parts = properties.getParts();
30 |
31 | test.deepEqual(parts[0].start, { x: 10, y: 0 }, "testing start points");
32 | test.deepEqual(parts[1].start, { x: 20, y: 0 }, "testing start points");
33 |
34 | test.deepEqual(parts[0].end, { x: 20, y: 0 }, "testing end points");
35 | test.deepEqual(parts[1].end, { x: 30, y: 0 }, "testing end points");
36 |
37 | test.equal(parts[0].length, 10, "testing lengths");
38 | test.equal(parts[1].length, 10, "testing lengths");
39 |
40 | //Testing functions
41 | test.deepEqual(
42 | parts[0].getPointAtLength(5),
43 | { x: 15, y: 0 },
44 | "testing getPointAtLength"
45 | );
46 | test.deepEqual(
47 | parts[1].getPointAtLength(5),
48 | { x: 25, y: 0 },
49 | "testing getPointAtLength"
50 | );
51 |
52 | test.deepEqual(
53 | parts[0].getTangentAtLength(5),
54 | { x: 1, y: 0 },
55 | "testing getTangentAtLength"
56 | );
57 | test.deepEqual(
58 | parts[1].getTangentAtLength(5),
59 | { x: 1, y: 0 },
60 | "testing getTangentAtLength"
61 | );
62 |
63 | test.deepEqual(
64 | parts[0].getPropertiesAtLength(5),
65 | { tangentX: 1, tangentY: 0, x: 15, y: 0 },
66 | "testing getPropertiesAtLength"
67 | );
68 | test.deepEqual(
69 | parts[1].getPropertiesAtLength(5),
70 | { tangentX: 1, tangentY: 0, x: 25, y: 0 },
71 | "testing getPropertiesAtLength"
72 | );
73 |
74 | test.end();
75 | });
76 |
77 | test("Testing the getParts with simple path", function (test) {
78 | var properties = new SVGPathProperties(
79 | "M100,200 C100,100 250,100 250,200 S400,300 400,200"
80 | );
81 | var parts = properties.getParts();
82 | test.equal(parts.length, 2, "Correct number of parts");
83 | test.deepEqual(
84 | parts[0].getPointAtLength(5),
85 | properties.getPointAtLength(5),
86 | "First part must have equal distances"
87 | );
88 | test.deepEqual(
89 | parts[1].getPointAtLength(5),
90 | properties.getPointAtLength(parts[0]["length"] + 5),
91 | "Second part must have equal distances"
92 | );
93 | test.end();
94 | });
95 |
96 | test("Issue 15", function (test) {
97 | var def = "M0,0 c 0.025,-0.052 0.081,-0.1387 0.2031,-0.2598 0,0 0,0 0,0";
98 | var properties = new SVGPathProperties(def);
99 | properties.getParts(); //The above path used to hang the programd
100 |
101 | def =
102 | "M0,0 c 0.025,-0.052 0.081,-0.1387 0.2031,-0.2598 0,0 0,0 0,0 c 0.1865,-0.31055 0.3632,-0.71289 0.5371,-1.22266 0.1963,-0.40625 0.3261,-0.78516 0.3857,-1.13184 0,-0.008 0,-0.0156 0,-0.0225";
103 | properties = new SVGPathProperties(def);
104 | properties.getParts();
105 |
106 | test.end();
107 | });
108 |
--------------------------------------------------------------------------------
/test/inDelta.ts:
--------------------------------------------------------------------------------
1 | export const inDelta = (
2 | actual: number[] | number,
3 | expected: number[] | number,
4 | delta: number
5 | ) => {
6 | if (Array.isArray(actual) && Array.isArray(expected))
7 | return inDeltaArray(actual, expected, delta);
8 | else if (!Array.isArray(actual) && !Array.isArray(expected))
9 | return inDeltaNumber(actual, expected, delta);
10 | else throw new Error("Both elements should be either arrays or numbers");
11 | };
12 |
13 | const inDeltaArray = (actual: number[], expected: number[], delta: number) => {
14 | const n = expected.length;
15 | let i = -1;
16 | if (actual.length !== n) return false;
17 | while (++i < n) if (!inDelta(actual[i], expected[i], delta)) return false;
18 | return true;
19 | };
20 |
21 | const inDeltaNumber = (actual: number, expected: number, delta: number) => {
22 | return actual >= expected - delta && actual <= expected + delta;
23 | };
24 |
--------------------------------------------------------------------------------
/test/index-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import { svgPathProperties } from "../src/index";
3 |
4 | test("Creation with different styles test", function (test) {
5 | const svgPath = "M0,100 q50,-150 100,0 t100,0";
6 | const a = new svgPathProperties(svgPath); // MyClass
7 | const b = new svgPathProperties(svgPath); // also MyClass
8 |
9 | test.equal(
10 | a.getTotalLength(),
11 | b.getTotalLength(),
12 | "Both methods must return the same and work: getTotalLength"
13 | );
14 | test.deepEqual(
15 | a.getPointAtLength(50),
16 | b.getPointAtLength(50),
17 | "Both methods must return the same and work: getPointAtLength"
18 | );
19 | test.deepEqual(
20 | a.getTangentAtLength(50),
21 | b.getTangentAtLength(50),
22 | "Both methods must return the same and work: getTangentAtLength"
23 | );
24 | test.deepEqual(
25 | a.getPropertiesAtLength(50),
26 | b.getPropertiesAtLength(50),
27 | "Both methods must return the same and work: getPropertiesAtLength"
28 | );
29 |
30 | test.deepEqual(a.getParts()[0].start, b.getParts()[0].start);
31 | test.end();
32 | });
33 |
--------------------------------------------------------------------------------
/test/length-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import { LinearPosition } from "../src/linear";
3 | import SVGPathProperties from "../src/svg-path-properties";
4 | import { inDelta } from "./inDelta";
5 |
6 | test("Testing LinearPosition directly", function (test) {
7 | let properties = new LinearPosition(0, 0, 10, 0);
8 | test.equal(properties.getTotalLength(), 10, "Length with m and l");
9 | test.end();
10 | });
11 |
12 | test("Testing the lineTo", function (test) {
13 | let properties = new SVGPathProperties("m0,0l10,0");
14 |
15 | test.equal(properties.getTotalLength(), 10, "Length with m and l");
16 |
17 | properties = new SVGPathProperties("M0,0L10,0");
18 | test.equal(properties.getTotalLength(), 10, "Length with M and L");
19 | properties = new SVGPathProperties("M0,0L10,0M0,0L10,0");
20 | test.equal(properties.getTotalLength(), 20, "Length with M and L");
21 | properties = new SVGPathProperties("M0,0L10,0m0,0L10,0");
22 | test.equal(properties.getTotalLength(), 10, "Length with m, M and L");
23 |
24 | properties = new SVGPathProperties("M0,0L10,0l10,0");
25 | test.equal(properties.getTotalLength(), 20, "Length with M, l and L");
26 | test.end();
27 | });
28 |
29 | test("Testing the H and V", function (test) {
30 | let properties = new SVGPathProperties("m0,0h10");
31 | test.equal(properties.getTotalLength(), 10, "Length with m and h");
32 |
33 | properties = new SVGPathProperties("M50,0H40");
34 | test.equal(properties.getTotalLength(), 10, "Length with m and H");
35 |
36 | properties = new SVGPathProperties("m0,0v10");
37 | test.equal(properties.getTotalLength(), 10, "Length with m and v");
38 |
39 | properties = new SVGPathProperties("M0,50V40");
40 | test.equal(properties.getTotalLength(), 10, "Length with m and V");
41 |
42 | test.end();
43 | });
44 |
45 | test("Testing z and Z", function (test) {
46 | let properties = new SVGPathProperties("m0,0h10z");
47 | test.equal(properties.getTotalLength(), 20, "Length with z");
48 |
49 | properties = new SVGPathProperties("m0,0h10Z");
50 | test.equal(properties.getTotalLength(), 20, "Length with Z");
51 | test.end();
52 | });
53 |
54 | test("Testing Cubic Bézier", function (test) {
55 | //C & c
56 | let properties = new SVGPathProperties("M100,25C10,90,110,100,150,195");
57 | test.true(inDelta(properties.getTotalLength(), 213.8, 0.1));
58 |
59 | properties = new SVGPathProperties("m100,25c-90,65,10,75,50,170");
60 | test.true(inDelta(properties.getTotalLength(), 213.8, 0.1));
61 |
62 | //S & s
63 | properties = new SVGPathProperties(
64 | "M100,200 C100,100 250,100 250,200 S400,300 400,200"
65 | );
66 | test.true(inDelta(properties.getTotalLength(), 475.746, 0.1));
67 |
68 | properties = new SVGPathProperties(
69 | "M100,200 c0,-100 150,-100 150,0 s150,100 150,0"
70 | );
71 | test.true(inDelta(properties.getTotalLength(), 475.746, 0.1));
72 |
73 | //S & s without previous C or c
74 | properties = new SVGPathProperties("M100,200 S400,300 400,200");
75 | test.true(inDelta(properties.getTotalLength(), 327.9618, 0.1));
76 |
77 | properties = new SVGPathProperties("M100,200 s300,100 300,0");
78 | test.true(inDelta(properties.getTotalLength(), 327.9618, 0.1));
79 |
80 | test.end();
81 | });
82 |
83 | test("Testing Quadratic Bézier", function (test) {
84 | //Q & q
85 | let properties = new SVGPathProperties("M200,300 Q400,50 600,300");
86 | test.true(inDelta(properties.getTotalLength(), 487.77, 0.1));
87 |
88 | properties = new SVGPathProperties("M200,300 q200,-250 400,0");
89 | test.true(inDelta(properties.getTotalLength(), 487.77, 0.1));
90 |
91 | //T & t
92 | properties = new SVGPathProperties("M0,100 Q50,-50 100,100 T200,100");
93 | test.true(inDelta(properties.getTotalLength(), 376.84, 0.1));
94 |
95 | properties = new SVGPathProperties("M0,100 q50,-150 100,0 t100,0");
96 | test.true(inDelta(properties.getTotalLength(), 376.84, 0.1));
97 |
98 | properties = new SVGPathProperties(
99 | "M0,100 Q50,-50 100,100 T200,100 T300,100"
100 | );
101 | test.true(inDelta(properties.getTotalLength(), 565.26, 0.1));
102 |
103 | //T & t without previous values
104 | properties = new SVGPathProperties("M0,100 T200,100");
105 | test.true(inDelta(properties.getTotalLength(), 200, 0.1));
106 |
107 | properties = new SVGPathProperties("M0,100 t200,100");
108 | test.true(inDelta(properties.getTotalLength(), 223.606, 0.1));
109 |
110 | test.end();
111 | });
112 |
113 | test("Testing Arcs", function (test) {
114 | //A & a
115 | let properties = new SVGPathProperties("M50,20A50,50,0,0,0,150,20");
116 | test.true(inDelta(properties.getTotalLength(), 157.1, 0.1));
117 |
118 | properties = new SVGPathProperties("M50,20A50,50,0,0,0,150,20Z");
119 | test.true(inDelta(properties.getTotalLength(), 257.1, 0.1));
120 |
121 | properties = new SVGPathProperties("M50,20a50,50,0,0,0,100,0");
122 | test.true(inDelta(properties.getTotalLength(), 157.1, 0.1));
123 | test.end();
124 | });
125 |
126 | test("Some complex examples", function (test) {
127 | let properties = new SVGPathProperties(
128 | "M137.69692698614858,194.75002119995685L140.5811864522362,200.02784443179866L145.21300688556522,205.5730786360974L151.96589957664872,210.57916233863872L157.11811791245674,216.958427402148L160.38007797705498,217.5517159659712L170.86150068075614,226.50677931755828L184.78753673995035,229.40372164152683L188.48682846625186,231.74464203758626L194.96220985606624,232.24831761753774L199.0151340580992,235.98908347947008L200.33619274822317,239.1501414459547L208.1352797340722,240.97174662891314L214.55451361971706,243.72269753526453L217.92992784370034,242.79750552259512L222.422382828094,245.95312239185364L226.33834281296274,246.6562900586742L232.1785094475572,250.37579609444018L247.67126011118384,253.41216989328635L249.86860925383274,259.67235659237457L258.0102758151366,263.53584756964034L265.7094539012957,271.9301187141604L275.3442092382522,280.797134878233L292.5367640425162,281.439215857073L300.3900165167456,283.19277126134665L317.1541418598862,288.08140107614616L325.68746219694265,282.98731281377525L334.20900545032936,279.42687578910136L341.89090086141164,279.65662234387565L344.6975683081848,280.71420717321774L352.73368224017975,278.81635544720564L357.8378453664788,280.8621873013037L360.27780217558785,280.351713437805L366.10835670115375,282.6140677325477L369.09298803246423,282.32880268111796L376.79699044083907,278.5755589629451L382.0884404158815,278.74374570898004L386.6969703376813,280.7868194847831L391.5118882394122,287.6851129793625L401.6043570144851,289.4523241399227L418.32264375071753,303.60974325767233L416.56748832810626,308.8321991418072L421.85304030224415,309.8073672357337L426.9233662531078,306.30064325383734L428.39794675453993,303.9729502861741L433.7178516894217,301.12745610964237L435.55518815288303,303.2790040699963L429.98849506106274,310.0981677440247L430.3920258191735,315.904266873991L431.8697365975619,320.41310652120495L431.51963155330213,325.7229788905284L437.6672507546333,329.58621381302714L437.3918696288182,334.8637567665635L439.98603260092784,334.44629338092415L446.1764597142119,341.8547790472293L453.6668527230894,346.9381545890387L457.5294853076264,347.9669234517022L462.48118856871827,352.94569484976665L466.87142760911547,353.62325409732335L470.1647323309724,356.65500849656917L478.52329558789495,361.73028232300277L486.88560554821527,370.7823973990582L489.73056770534674,376.3046557640006L489.2413765676388,379.0217789927731L492.6796339000674,384.9123226146289L500.3373626256565,376.6596349946864L507.84942333888387,380.4063594074064L511.8061547036337,380.01502900094323"
129 | );
130 | test.true(inDelta(properties.getTotalLength(), 498.031, 0.1));
131 | properties = new SVGPathProperties(
132 | "M240,100C290,100,240,225,290,200S290,75,340,50S515,100,390,150S215,200,90,150S90,25,140,50S140,175,190,200S190,100,240,100"
133 | );
134 | test.true(inDelta(properties.getTotalLength(), 1329.45, 0.1));
135 | properties = new SVGPathProperties(
136 | "m240,100c50,0,0,125,50,100s0,-125,50,-150s175,50,50,100s-175,50,-300,0s0,-125,50,-100s0,125,50,150s0,-100,50,-100"
137 | );
138 | test.true(inDelta(properties.getTotalLength(), 1329.45, 0.1));
139 | properties = new SVGPathProperties("M100,100h100v100h-100Z m200,0h1v1h-1z");
140 | test.true(inDelta(properties.getTotalLength(), 404, 0.1));
141 | //https://github.com/rveciana/svg-path-properties/issues/12. getQuadraticArcLength zero division
142 | properties = new SVGPathProperties("M470,623Q468,627,467,629");
143 | test.true(inDelta(properties.getTotalLength(), 6.71, 0.1));
144 | properties = new SVGPathProperties(
145 | "M 408 666 Q 408 569 276 440 Q 270 435 270 429 Q 269 427 270 426 Q 271 426 274 426 Q 347 445 443 578 Q 456 598 466 609 Q 472 616 470 623 Q 468 627 467 629"
146 | );
147 | test.true(inDelta(properties.getTotalLength(), 580, 0.1));
148 |
149 | //https://github.com/rveciana/svg-path-properties/issues/14
150 | properties = new SVGPathProperties(
151 | "M0,0L31.081620209059235,726.1062992125984Q41.44216027874565,726.1062992125984,41.44216027874565,726.1062992125984"
152 | );
153 | test.true(inDelta(properties.getTotalLength(), 737.13, 0.1));
154 |
155 | //https://github.com/rveciana/svg-path-properties/issues/32
156 | properties = new SVGPathProperties("M270 830 Q270 830 270 830");
157 | test.true(inDelta(properties.getTotalLength(), 0, 0.1));
158 |
159 | test.end();
160 | });
161 |
--------------------------------------------------------------------------------
/test/parse-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import parse from "../src/parse";
3 |
4 | test("overloaded moveTo", (test) => {
5 | test.deepEqual(parse("m 12.5,52 39,0 0,-40 -39,0 z"), [
6 | ["m", 12.5, 52],
7 | ["l", 39, 0],
8 | ["l", 0, -40],
9 | ["l", -39, 0],
10 | ["z"],
11 | ]);
12 |
13 | test.deepEqual(parse("M 12.5,52 39,0 0,-40 -39,0 z"), [
14 | ["M", 12.5, 52],
15 | ["L", 39, 0],
16 | ["L", 0, -40],
17 | ["L", -39, 0],
18 | ["z"],
19 | ]);
20 |
21 | test.deepEqual(parse("z"), [["z"]]);
22 | test.end();
23 | });
24 |
25 | test("curveTo", function (test) {
26 | const a = parse("c 50,0 50,100 100,100 50,0 50,-100 100,-100");
27 | const b = parse("c 50,0 50,100 100,100 c 50,0 50,-100 100,-100");
28 |
29 | test.deepEqual(
30 | a,
31 | [
32 | ["c", 50, 0, 50, 100, 100, 100],
33 | ["c", 50, 0, 50, -100, 100, -100],
34 | ],
35 | "Correct parse c"
36 | );
37 | test.deepEqual(a, b, "Testing repeated");
38 | test.end();
39 | });
40 |
41 | test("lineTo, h, v, l", function (test) {
42 | test.throws(function () {
43 | parse("l 10 10 0");
44 | }, /Malformed/);
45 | test.deepEqual(parse("l 10,10"), [["l", 10, 10]]);
46 | test.deepEqual(parse("L 10,10"), [["L", 10, 10]]);
47 | test.deepEqual(parse("l10 10 10 10"), [
48 | ["l", 10, 10],
49 | ["l", 10, 10],
50 | ]);
51 |
52 | test.deepEqual(parse("h 10.5"), [["h", 10.5]]);
53 | test.deepEqual(parse("v 10.5"), [["v", 10.5]]);
54 |
55 | test.end();
56 | });
57 |
58 | test("arcTo, quadratic curveTo, smooth curveTo, smooth quadratic curveTo", function (test) {
59 | test.deepEqual(parse("A 30 50 0 0 1 162.55 162.45"), [
60 | ["A", 30, 50, 0, 0, 1, 162.55, 162.45],
61 | ]);
62 |
63 | test.deepEqual(parse('a30 50 0 01162.55 162.45'), [
64 | ['a', 30, 50, 0, 0, 1, 162.55, 162.45],
65 | ]);
66 |
67 | test.deepEqual(parse('a95500 95500 0 01-219.59 1128.84'), [
68 | ['a', 95500, 95500, 0, 0, 1, -219.59, 1128.84],
69 | ]);
70 |
71 | test.deepEqual(parse("M10 80 Q 95 10 180 80"), [
72 | ["M", 10, 80],
73 | ["Q", 95, 10, 180, 80],
74 | ]);
75 | test.deepEqual(parse("S 1 2, 3 4"), [["S", 1, 2, 3, 4]]);
76 | test.deepEqual(parse("T 1 -2e2"), [["T", 1, -2e2]]);
77 |
78 | test.throws(function () {
79 | parse("t 1 2 3");
80 | }, Error);
81 | test.end();
82 | });
83 |
84 | test("Empty string", (test) => {
85 | test.deepEqual(parse(""), [["M", 0, 0]]);
86 |
87 | test.end();
88 | });
89 |
--------------------------------------------------------------------------------
/test/point-at-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import SVGPathProperties from "../src/svg-path-properties";
3 | import { inDelta } from "./inDelta";
4 |
5 | test("getPointAtLength testing lineTo", function (test) {
6 | const paths = [
7 | {
8 | path: "M0,50L500,50",
9 | xValues: [0, 100, 200, 300, 400, 500],
10 | yValues: [50, 50, 50, 50, 50, 50],
11 | },
12 | {
13 | path: "M0,50L300,300",
14 | xValues: [
15 | 0,
16 | 59.999996185302734,
17 | 119.99999237060547,
18 | 180,
19 | 239.99998474121094,
20 | 300,
21 | ],
22 | yValues: [50, 100, 150, 200, 249.99998474121094, 300],
23 | },
24 | {
25 | path: "M0,50H300",
26 | xValues: [0, 50, 100, 150, 200, 250, 300],
27 | yValues: [50, 50, 50, 50, 50, 50, 50],
28 | },
29 | {
30 | path: "M50,50h300",
31 | xValues: [50, 100, 150, 200, 250, 300, 350],
32 | yValues: [50, 50, 50, 50, 50, 50, 50],
33 | },
34 | {
35 | path: "M50,0V200",
36 | xValues: [50, 50, 50, 50, 50, 50, 50],
37 | yValues: [
38 | 0,
39 | 33.33333206176758,
40 | 66.66666412353516,
41 | 100,
42 | 133.3333282470703,
43 | 166.6666717529297,
44 | 200,
45 | ],
46 | },
47 | {
48 | path: "M50,10v200",
49 | xValues: [50, 50, 50, 50, 50, 50, 50],
50 | yValues: [
51 | 10,
52 | 43.33333206176758,
53 | 76.66666412353516,
54 | 110,
55 | 143.3333282470703,
56 | 176.6666717529297,
57 | 210,
58 | ],
59 | },
60 | {
61 | path: "M50,50H300V200H50Z",
62 | xValues: [50, 183.3333282470703, 300, 300, 166.66668701171875, 50, 50],
63 | yValues: [50, 50, 66.66665649414062, 200, 200, 183.33331298828125, 50],
64 | },
65 | ];
66 | let properties: SVGPathProperties;
67 | for (let i = 0; i < paths.length; i++) {
68 | properties = new SVGPathProperties(paths[i].path);
69 | for (let j = 0; j < paths[i].xValues.length; j++) {
70 | const position = properties.getPointAtLength(
71 | (j * properties.getTotalLength()) / (paths[i].xValues.length - 1)
72 | );
73 | test.true(inDelta(position.x, paths[i].xValues[j], 0.1));
74 | test.true(inDelta(position.y, paths[i].yValues[j], 0.1));
75 | }
76 | test.deepEqual(
77 | properties.getPointAtLength(10000000),
78 | properties.getPointAtLength(properties.getTotalLength())
79 | );
80 | test.deepEqual(
81 | properties.getPointAtLength(-1),
82 | properties.getPointAtLength(0)
83 | );
84 | }
85 |
86 | test.end();
87 | });
88 |
89 | test("getPointAtLength testing Quadratic Bézier", function (test) {
90 | const paths = [
91 | {
92 | path: "M200,300 Q400,50 600,300",
93 | xValues: [
94 | 200,
95 | 255.24655151367188,
96 | 321.72381591796875,
97 | 400.0000305175781,
98 | 478.2762756347656,
99 | 544.75341796875,
100 | 600,
101 | ],
102 | yValues: [
103 | 300,
104 | 240.47999572753906,
105 | 194.14747619628906,
106 | 175.0000762939453,
107 | 194.1474609375,
108 | 240.47999572753906,
109 | 300,
110 | ],
111 | },
112 | {
113 | path: "M0,100 Q50,-50 100,100 T200,100",
114 | xValues: [
115 | 0,
116 | 25.60834312438965,
117 | 74.3916015625,
118 | 99.99996948242188,
119 | 125.60824584960938,
120 | 174.39163208007812,
121 | 200,
122 | ],
123 | yValues: [
124 | 100,
125 | 42.84862518310547,
126 | 42.84857940673828,
127 | 99.99991607666016,
128 | 157.15122985839844,
129 | 157.15139770507812,
130 | 100,
131 | ],
132 | },
133 | {
134 | path: "M0,100 q50,-150 100,0 t100,0",
135 | xValues: [
136 | 0,
137 | 25.60834312438965,
138 | 74.3916015625,
139 | 99.99996948242188,
140 | 125.60824584960938,
141 | 174.39163208007812,
142 | 200,
143 | ],
144 | yValues: [
145 | 100,
146 | 42.84862518310547,
147 | 42.84857940673828,
148 | 99.99991607666016,
149 | 157.15122985839844,
150 | 157.15139770507812,
151 | 100,
152 | ],
153 | },
154 | {
155 | path: "M0,100 T200,100",
156 | xValues: [
157 | 0,
158 | 33.33333206176758,
159 | 66.66666412353516,
160 | 100,
161 | 133.3333282470703,
162 | 166.6666717529297,
163 | 200,
164 | ],
165 | yValues: [100, 100, 100, 100, 100, 100, 100],
166 | },
167 | {
168 | path: "M0,100 Q50,-50 100,100 T200,100 T300,100",
169 | xValues: [
170 | 0,
171 | 50.00000762939453,
172 | 99.99998474121094,
173 | 149.9999542236328,
174 | 200.0000457763672,
175 | 250.00059509277344,
176 | 300,
177 | ],
178 | yValues: [
179 | 100,
180 | 25.000080108642578,
181 | 99.99996185302734,
182 | 174.9999237060547,
183 | 99.99983978271484,
184 | 25.00008201599121,
185 | 100,
186 | ],
187 | },
188 | ];
189 | let properties: SVGPathProperties;
190 | for (let i = 0; i < paths.length; i++) {
191 | properties = new SVGPathProperties(paths[i].path);
192 | for (let j = 0; j < paths[i].xValues.length; j++) {
193 | const position = properties.getPointAtLength(
194 | (j * properties.getTotalLength()) / (paths[i].xValues.length - 1)
195 | );
196 | test.true(inDelta(position.x, paths[i].xValues[j], 1));
197 | test.true(inDelta(position.y, paths[i].yValues[j], 1));
198 | }
199 | test.deepEqual(
200 | properties.getPointAtLength(10000000),
201 | properties.getPointAtLength(properties.getTotalLength())
202 | );
203 | test.deepEqual(
204 | properties.getPointAtLength(-1),
205 | properties.getPointAtLength(0)
206 | );
207 | }
208 | test.end();
209 | });
210 |
211 | test("getPointAtLength testing Cubic Bézier", function (test) {
212 | const paths = [
213 | {
214 | path: "M200,200 C275,100 575,100 500,200",
215 | xValues: [
216 | 200,
217 | 249.48426818847656,
218 | 309.1169738769531,
219 | 371.97515869140625,
220 | 435.7851257324219,
221 | 496.41815185546875,
222 | 500.0001220703125,
223 | ],
224 | yValues: [
225 | 200,
226 | 160.3770294189453,
227 | 137.765380859375,
228 | 126.64154052734375,
229 | 126.40363311767578,
230 | 144.5059051513672,
231 | 199.99981689453125,
232 | ],
233 | },
234 | {
235 | path: "M100,200 C100,100 250,100 250,200 S400,300 400,200",
236 | xValues: [
237 | 100,
238 | 136.8885955810547,
239 | 213.11134338378906,
240 | 250,
241 | 286.88836669921875,
242 | 363.11114501953125,
243 | 400,
244 | ],
245 | yValues: [
246 | 200,
247 | 134.37181091308594,
248 | 134.3717498779297,
249 | 199.99984741210938,
250 | 265.6280517578125,
251 | 265.62835693359375,
252 | 200,
253 | ],
254 | },
255 | {
256 | path: "M100,200 S400,300 400,200",
257 | xValues: [
258 | 100,
259 | 152.38723754882812,
260 | 205.42906188964844,
261 | 259.1198425292969,
262 | 313.48455810546875,
263 | 367.6199951171875,
264 | 400,
265 | ],
266 | yValues: [
267 | 200,
268 | 215.58023071289062,
269 | 228.76190185546875,
270 | 238.95660400390625,
271 | 244.3085174560547,
272 | 238.78338623046875,
273 | 200,
274 | ],
275 | },
276 | {
277 | path:
278 | "M240,100C290,100,240,225,290,200S290,75,340,50S515,100,390,150S215,200,90,150S90,25,140,50S140,175,190,200S190,100,240,100",
279 | xValues: [
280 | 240,
281 | 315.0015563964844,
282 | 441.4165954589844,
283 | 240.0000762939453,
284 | 38.58317947387695,
285 | 164.99853515625,
286 | 240,
287 | ],
288 | yValues: [
289 | 100,
290 | 121.3836898803711,
291 | 111.11810302734375,
292 | 187.49990844726562,
293 | 111.11775207519531,
294 | 121.38365936279297,
295 | 100,
296 | ],
297 | },
298 | {
299 | path:
300 | "m240,100c50,0,0,125,50,100s0,-125,50,-150s175,50,50,100s-175,50,-300,0s0,-125,50,-100s0,125,50,150s0,-100,50,-100",
301 | xValues: [
302 | 240,
303 | 315.0015563964844,
304 | 441.4165954589844,
305 | 240.0000762939453,
306 | 38.58317947387695,
307 | 164.99853515625,
308 | 240,
309 | ],
310 | yValues: [
311 | 100,
312 | 121.3836898803711,
313 | 111.11810302734375,
314 | 187.49990844726562,
315 | 111.11775207519531,
316 | 121.38365936279297,
317 | 100,
318 | ],
319 | },
320 | ];
321 | let properties: SVGPathProperties;
322 | for (let i = 0; i < paths.length; i++) {
323 | properties = new SVGPathProperties(paths[i].path);
324 | for (let j = 0; j < paths[i].xValues.length; j++) {
325 | const position = properties.getPointAtLength(
326 | (j * properties.getTotalLength()) / (paths[i].xValues.length - 1)
327 | );
328 | test.true(inDelta(position.x, paths[i].xValues[j], 1));
329 | test.true(inDelta(position.y, paths[i].yValues[j], 1));
330 | }
331 | test.deepEqual(
332 | properties.getPointAtLength(10000000),
333 | properties.getPointAtLength(properties.getTotalLength())
334 | );
335 | test.deepEqual(
336 | properties.getPointAtLength(-1),
337 | properties.getPointAtLength(0)
338 | );
339 | }
340 | test.end();
341 | });
342 |
343 | test("getPointAtLength bug testing", function (test) {
344 | //Testing https://github.com/rveciana/svg-path-properties/issues/1
345 | const properties = new SVGPathProperties(
346 | "M 211.6687111164928,312.6478542077994 C 211.6687111164928,312.6478542077994 211.6687111164928,312.6478542077994 219,293"
347 | );
348 | const pos1 = properties.getPointAtLength(12);
349 | const pos2 = properties.getPointAtLength(11.95);
350 | const pos3 = properties.getPointAtLength(12.05);
351 | test.true(inDelta(pos1.x, pos2.x, 0.1));
352 | test.true(inDelta(pos1.x, pos3.x, 0.1));
353 | test.true(inDelta(pos1.y, pos2.y, 0.1));
354 | test.true(inDelta(pos1.y, pos3.y, 0.1));
355 |
356 | test.end();
357 | });
358 |
359 | test("Testing getPointAtLength with straigh line bezier curve (bug)", function (test) {
360 | //https://github.com/rveciana/svg-path-properties/issues/4
361 | const pathData = new SVGPathProperties("M500,300Q425,325 350,350");
362 | const pathLen = pathData.getTotalLength();
363 | test.true(inDelta(pathLen, 158.11, 0.1)); //Gave undefined
364 |
365 | let pos = pathData.getPointAtLength(0);
366 | test.true(inDelta(pos.x, 500, 0.00001));
367 | test.true(inDelta(pos.y, 300, 0.00001));
368 | pos = pathData.getPointAtLength(pathLen);
369 | test.true(inDelta(pos.x, 350, 0.00001));
370 | test.true(inDelta(pos.y, 350, 0.00001));
371 |
372 | test.end();
373 | });
374 |
375 | test("Testing with multiple rings", function (test) {
376 | let properties = new SVGPathProperties(
377 | "M100,100h100v100h-100Z m200,0h1v1h-1z"
378 | );
379 | test.deepEqual(properties.getPointAtLength(0), { x: 100, y: 100 });
380 | test.deepEqual(properties.getPointAtLength(401), { x: 301, y: 100 });
381 |
382 | properties = new SVGPathProperties("M100,100L200,100 M300,100L400,100");
383 | test.deepEqual(properties.getPointAtLength(0), { x: 100, y: 100 });
384 | test.deepEqual(properties.getPointAtLength(100), { x: 200, y: 100 });
385 | test.deepEqual(properties.getPointAtLength(200), { x: 400, y: 100 });
386 | test.deepEqual(
387 | properties.getPointAtLength(200),
388 | properties.getPointAtLength(500)
389 | );
390 | properties = new SVGPathProperties(
391 | "M100,100 L101,100 M200,0 M500,600 M0,0L1,0L1,1L0,1Z"
392 | );
393 | test.deepEqual(properties.getPointAtLength(0), { x: 100, y: 100 });
394 | test.deepEqual(properties.getPointAtLength(1), { x: 101, y: 100 });
395 | test.deepEqual(properties.getPointAtLength(2), { x: 1, y: 0 });
396 | test.end();
397 | });
398 |
399 | test("TestingDegenerated quadratic curves, issue 9", function (test) {
400 | let properties = new SVGPathProperties("M60,20Q60,20 150,20");
401 | test.deepEqual(properties.getPointAtLength(2), { x: 62, y: 20 });
402 | test.equal(properties.getTotalLength(), 90);
403 | properties = new SVGPathProperties("M60,20q0,0 90,0");
404 | test.deepEqual(properties.getPointAtLength(2), { x: 62, y: 20 });
405 | test.equal(properties.getTotalLength(), 90);
406 | test.end();
407 | });
408 |
409 | test("Check null path, issue 35", function (test) {
410 | let properties = new SVGPathProperties("M0, 0");
411 | test.deepEqual(properties.getPointAtLength(0), { x: 0, y: 0 });
412 | test.deepEqual(properties.getTangentAtLength(0), { x: 0, y: 0 });
413 | test.deepEqual(properties.getPropertiesAtLength(0), {
414 | x: 0,
415 | y: 0,
416 | tangentX: 0,
417 | tangentY: 0,
418 | });
419 |
420 | properties = new SVGPathProperties("");
421 | test.deepEqual(properties.getPointAtLength(0), { x: 0, y: 0 });
422 | test.deepEqual(properties.getTangentAtLength(0), { x: 0, y: 0 });
423 | test.deepEqual(properties.getPropertiesAtLength(0), {
424 | x: 0,
425 | y: 0,
426 | tangentX: 0,
427 | tangentY: 0,
428 | });
429 | properties = new SVGPathProperties("M10, 10");
430 | test.deepEqual(properties.getPointAtLength(0), { x: 10, y: 10 });
431 | test.deepEqual(properties.getTangentAtLength(0), { x: 0, y: 0 });
432 | test.deepEqual(properties.getPropertiesAtLength(0), {
433 | x: 10,
434 | y: 10,
435 | tangentX: 0,
436 | tangentY: 0,
437 | });
438 |
439 | test.end();
440 | });
441 | test("TestingDegenerated quadratic curves, issue 43", function (test) {
442 | let properties = new SVGPathProperties(
443 | "M224,32C153,195,69,366,76,544C77,567,97,585,105,606C133,683,137,768,175,840C193,875,225,902,250,932"
444 | );
445 | test.deepEqual(properties.getPointAtLength(300), {
446 | x: 111.7377391058728,
447 | y: 310.0179550672576,
448 | });
449 |
450 | test.deepEqual(properties.getTangentAtLength(300), {
451 | x: -0.29770950875462776,
452 | y: 0.9546565080682572,
453 | });
454 |
455 | test.deepEqual(properties.getPropertiesAtLength(300), {
456 | x: 111.7377391058728,
457 | y: 310.0179550672576,
458 | tangentX: -0.29770950875462776,
459 | tangentY: 0.9546565080682572,
460 | });
461 |
462 | test.end();
463 | });
464 |
465 | //https://github.com/rveciana/svg-path-properties/pull/44/files
466 | test("Testing first point of zero length fraction", function (test) {
467 | let properties = new SVGPathProperties("M 0,0 l 0 0 l 10 10");
468 | test.deepEqual(properties.getPointAtLength(0), { x: 0, y: 0 });
469 |
470 | properties = new SVGPathProperties("M 1,1 L 1 1");
471 | test.deepEqual(properties.getPointAtLength(0), { x: 1, y: 1 });
472 |
473 | properties = new SVGPathProperties("M 1,1 l 0 0");
474 | test.deepEqual(properties.getPointAtLength(0), { x: 1, y: 1 });
475 |
476 | test.end();
477 | });
478 |
--------------------------------------------------------------------------------
/test/tangent-test.ts:
--------------------------------------------------------------------------------
1 | import test from "tape";
2 | import SVGPathProperties from "../src/svg-path-properties";
3 | import { inDelta } from "./inDelta";
4 |
5 | test("getTangentAtLength testing", function (test) {
6 | const paths = [
7 | {
8 | path: "M0,50L500,50",
9 | xValues: [1, 1, 1, 1, 1, 1, 1],
10 | yValues: [0, 0, 0, 0, 0, 0, 0],
11 | },
12 | {
13 | path: "M0,50L300,300",
14 | xValues: [
15 | 0.7682212795973759,
16 | 0.7682212795973759,
17 | 0.7682212795973759,
18 | 0.7682212795973759,
19 | 0.7682212795973759,
20 | 0.7682212795973759,
21 | 0.7682212795973759,
22 | ],
23 | yValues: [
24 | 0.6401843996644798,
25 | 0.6401843996644798,
26 | 0.6401843996644798,
27 | 0.6401843996644798,
28 | 0.6401843996644798,
29 | 0.6401843996644798,
30 | 0.6401843996644798,
31 | ],
32 | },
33 | {
34 | path: "M0,50H300",
35 | xValues: [1, 1, 1, 1, 1, 1, 1],
36 | yValues: [0, 0, 0, 0, 0, 0, 0],
37 | },
38 | {
39 | path: "M50,50h300",
40 | xValues: [1, 1, 1, 1, 1, 1, 1],
41 | yValues: [0, 0, 0, 0, 0, 0, 0],
42 | },
43 | {
44 | path: "M50,0V200",
45 | xValues: [0, 0, 0, 0, 0, 0, 0],
46 | yValues: [1, 1, 1, 1, 1, 1, 1],
47 | },
48 | {
49 | path: "M50,10v200",
50 | xValues: [0, 0, 0, 0, 0, 0, 0],
51 | yValues: [1, 1, 1, 1, 1, 1, 1],
52 | },
53 | {
54 | path: "M50,50H300V200H50Z",
55 | xValues: [1, 1, 0, 0, -1, 0, 0],
56 | yValues: [0, 0, 1, 1, 0, -1, -1],
57 | },
58 | {
59 | path: "M200,300 Q400,50 600,300",
60 | xValues: [
61 | 0.6246950475544243,
62 | 0.7418002077808687,
63 | 0.8984910301093634,
64 | 0.9999999999999247,
65 | 0.8984912864152661,
66 | 0.7418004257503856,
67 | 0.6246951852934535,
68 | ],
69 | yValues: [
70 | -0.7808688094430304,
71 | -0.6706209448982786,
72 | -0.43899187784401555,
73 | -3.8801383766437346e-7,
74 | 0.4389913532586264,
75 | 0.6706207037935429,
76 | 0.7808686992517869,
77 | ],
78 | },
79 | {
80 | path: "M0,100 Q50,-50 100,100 T200,100",
81 | xValues: [
82 | 0.3162277660168379,
83 | 0.564254701134953,
84 | 0.5642557779681502,
85 | 0.3162278830087659,
86 | 0.5642536243071706,
87 | 0.5642568548067621,
88 | 0.3162280000014775,
89 | ],
90 | yValues: [
91 | -0.9486832980505138,
92 | -0.8256007704981294,
93 | 0.8256000345382487,
94 | 0.9486832590531965,
95 | 0.8256015064522486,
96 | -0.8255992985726069,
97 | -0.948683220055602,
98 | ],
99 | },
100 | {
101 | path: "M0,100 q50,-150 100,0 t100,0",
102 | xValues: [
103 | 0.3162277660168379,
104 | 0.564254701134953,
105 | 0.5642557779681502,
106 | 0.3162278830087659,
107 | 0.5642536243071706,
108 | 0.5642568548067621,
109 | 0.3162280000014775,
110 | ],
111 | yValues: [
112 | -0.9486832980505138,
113 | -0.8256007704981294,
114 | 0.8256000345382487,
115 | 0.9486832590531965,
116 | 0.8256015064522486,
117 | -0.8255992985726069,
118 | -0.948683220055602,
119 | ],
120 | },
121 | {
122 | path: "M0,100 T200,100",
123 | xValues: [1, 1, 1, 1, 1, 1, 1],
124 | yValues: [0, 0, 0, 0, 0, 0, 0],
125 | },
126 | {
127 | path: "M0,100 Q50,-50 100,100 T200,100 T300,100",
128 | xValues: [
129 | 0.3162277660168379,
130 | 0.9999999999982623,
131 | 0.3162278434645575,
132 | 0.9999999999843601,
133 | 0.3162279209126205,
134 | 0.999999999956556,
135 | 0.31622799836102694,
136 | ],
137 | yValues: [
138 | -0.9486832980505138,
139 | -0.000001864275757835856,
140 | 0.9486832722346038,
141 | 0.000005592827273145598,
142 | -0.9486832464185723,
143 | -0.000009321378787369434,
144 | 0.948683220602419,
145 | ],
146 | },
147 | {
148 | path: "M200,200 C275,100 575,100 500,200",
149 | xValues: [
150 | 0.6,
151 | 0.8855412510377398,
152 | 0.9662834030858953,
153 | 0.9955306269200779,
154 | 0.9950635163752912,
155 | 0.8102625798004855,
156 | -0.5999966647640088,
157 | ],
158 | yValues: [
159 | -0.8,
160 | -0.4645607524431165,
161 | -0.25748084379374897,
162 | -0.09443924430085592,
163 | 0.09924010468979105,
164 | 0.5860670198663817,
165 | 0.8000025014161303,
166 | ],
167 | },
168 | {
169 | path: "M100,200 C100,100 250,100 250,200 S400,300 400,200",
170 | xValues: [
171 | 0,
172 | 0.8804617844502954,
173 | 0.8804628147711906,
174 | 0.0000014914995832878143,
175 | 0.8804607541243551,
176 | 0.8804638450870454,
177 | 0.0000029830040755489834,
178 | ],
179 | yValues: [
180 | -1,
181 | -0.47411712278993,
182 | 0.47411520942192104,
183 | 0.9999999999988878,
184 | 0.47411903615734735,
185 | -0.47411329605331204,
186 | -0.9999999999955509,
187 | ],
188 | },
189 | {
190 | path: "M100,200 S400,300 400,200",
191 | xValues: [
192 | 0,
193 | 0.9647425496827284,
194 | 0.9760648482761272,
195 | 0.9886843239589528,
196 | 0.9995932816004552,
197 | 0.953018740113177,
198 | 2.2070215559197298e-7,
199 | ],
200 | yValues: [
201 | 0,
202 | 0.26319538907752243,
203 | 0.21747968171693818,
204 | 0.15001102478760836,
205 | 0.028517913304325987,
206 | -0.30291134180332835,
207 | -0.9999999999999756,
208 | ],
209 | },
210 | {
211 | path:
212 | "M240,100C290,100,240,225,290,200S290,75,340,50S515,100,390,150S215,200,90,150S90,25,140,50S140,175,190,200S190,100,240,100",
213 | xValues: [
214 | 1,
215 | 0.0011574896425055297,
216 | -0.3871202612806902,
217 | -0.999999999999897,
218 | -0.3871144077881202,
219 | 0.001157352564866387,
220 | 1,
221 | ],
222 | yValues: [
223 | 0,
224 | -0.9999993301086394,
225 | 0.9220292312643728,
226 | -4.5391975842908476e-7,
227 | -0.9220316888712953,
228 | 0.999999330267296,
229 | -2.2204460492503162e-15,
230 | ],
231 | },
232 | {
233 | path:
234 | "m240,100c50,0,0,125,50,100s0,-125,50,-150s175,50,50,100s-175,50,-300,0s0,-125,50,-100s0,125,50,150s0,-100,50,-100",
235 | xValues: [
236 | 1,
237 | 0.0011574896425055297,
238 | -0.3871202612806902,
239 | -0.999999999999897,
240 | -0.3871144077881202,
241 | 0.001157352564866387,
242 | 1,
243 | ],
244 | yValues: [
245 | 0,
246 | -0.9999993301086394,
247 | 0.9220292312643728,
248 | -4.5391975842908476e-7,
249 | -0.9220316888712953,
250 | 0.999999330267296,
251 | -2.2204460492503162e-15,
252 | ],
253 | },
254 | ];
255 |
256 | for (let i = 0; i < paths.length; i++) {
257 | let properties = new SVGPathProperties(paths[i].path);
258 | for (let j = 0; j < paths[i].xValues.length; j++) {
259 | var tangent = properties.getTangentAtLength(
260 | (j * properties.getTotalLength()) / (paths[i].xValues.length - 1)
261 | );
262 |
263 | test.true(inDelta(tangent.x, paths[i].xValues[j], 0.1));
264 | test.true(inDelta(tangent.y, paths[i].yValues[j], 0.1));
265 | }
266 |
267 | test.deepEqual(
268 | properties.getTangentAtLength(10000000),
269 | properties.getTangentAtLength(properties.getTotalLength())
270 | );
271 | test.deepEqual(
272 | properties.getTangentAtLength(-1),
273 | properties.getTangentAtLength(0)
274 | );
275 | }
276 |
277 | test.end();
278 | });
279 |
--------------------------------------------------------------------------------
/test/test-getLength.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
164 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "noFallthroughCasesInSwitch": true,
5 | "noUnusedParameters": true,
6 | "noImplicitReturns": true,
7 | "moduleResolution": "node",
8 | "esModuleInterop": true,
9 | "noUnusedLocals": true,
10 | "noImplicitAny": true,
11 | "declarationDir": "dist/types",
12 | "declaration": true,
13 | "target": "es2015",
14 | "module": "es2015",
15 | "strict": true
16 | },
17 | "include": ["src/**/*"],
18 | "exclude": ["node_modules", "dist"]
19 | }
20 |
--------------------------------------------------------------------------------