• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thednp / svg-path-commander / 15316798182

29 May 2025 05:13AM UTC coverage: 100.0% (+2.2%) from 97.839%
15316798182

push

github

thednp
update deps, workflows, tests, bump

729 of 729 branches covered (100.0%)

Branch coverage included in aggregate %.

1400 of 1400 relevant lines covered (100.0%)

3288.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/math/bezier.ts
1
import type {
2
  CubicCoordinates,
3
  CubicPoints,
4
  DeriveCallback,
5
  DerivedCubicPoints,
6
  DerivedPoint,
7
  DerivedQuadPoints,
8
  PointTuple,
9
  QuadCoordinates,
10
  QuadPoints,
11
} from "../types";
12

13
/**
14
 * Tools from bezier.js by Mike 'Pomax' Kamermans
15
 * @see https://github.com/Pomax/bezierjs
16
 */
17

18
const Tvalues = [
2✔
19
  -0.0640568928626056260850430826247450385909,
20
  0.0640568928626056260850430826247450385909,
21
  -0.1911188674736163091586398207570696318404,
22
  0.1911188674736163091586398207570696318404,
23
  -0.3150426796961633743867932913198102407864,
24
  0.3150426796961633743867932913198102407864,
25
  -0.4337935076260451384870842319133497124524,
26
  0.4337935076260451384870842319133497124524,
27
  -0.5454214713888395356583756172183723700107,
28
  0.5454214713888395356583756172183723700107,
29
  -0.6480936519369755692524957869107476266696,
30
  0.6480936519369755692524957869107476266696,
31
  -0.7401241915785543642438281030999784255232,
32
  0.7401241915785543642438281030999784255232,
33
  -0.8200019859739029219539498726697452080761,
34
  0.8200019859739029219539498726697452080761,
35
  -0.8864155270044010342131543419821967550873,
36
  0.8864155270044010342131543419821967550873,
37
  -0.9382745520027327585236490017087214496548,
38
  0.9382745520027327585236490017087214496548,
39
  -0.9747285559713094981983919930081690617411,
40
  0.9747285559713094981983919930081690617411,
41
  -0.9951872199970213601799974097007368118745,
42
  0.9951872199970213601799974097007368118745,
43
];
44

45
const Cvalues = [
2✔
46
  0.1279381953467521569740561652246953718517,
47
  0.1279381953467521569740561652246953718517,
48
  0.1258374563468282961213753825111836887264,
49
  0.1258374563468282961213753825111836887264,
50
  0.121670472927803391204463153476262425607,
51
  0.121670472927803391204463153476262425607,
52
  0.1155056680537256013533444839067835598622,
53
  0.1155056680537256013533444839067835598622,
54
  0.1074442701159656347825773424466062227946,
55
  0.1074442701159656347825773424466062227946,
56
  0.0976186521041138882698806644642471544279,
57
  0.0976186521041138882698806644642471544279,
58
  0.086190161531953275917185202983742667185,
59
  0.086190161531953275917185202983742667185,
60
  0.0733464814110803057340336152531165181193,
61
  0.0733464814110803057340336152531165181193,
62
  0.0592985849154367807463677585001085845412,
63
  0.0592985849154367807463677585001085845412,
64
  0.0442774388174198061686027482113382288593,
65
  0.0442774388174198061686027482113382288593,
66
  0.0285313886289336631813078159518782864491,
67
  0.0285313886289336631813078159518782864491,
68
  0.0123412297999871995468056670700372915759,
69
  0.0123412297999871995468056670700372915759,
70
];
71

72
/**
73
 * @param points
74
 * @returns
75
 */
76
const deriveBezier = (points: QuadPoints | CubicPoints) => {
2✔
77
  const dpoints = [] as (DerivedCubicPoints | DerivedQuadPoints)[];
5,251✔
78
  for (let p = points, d = p.length, c = d - 1; d > 1; d -= 1, c -= 1) {
5,251✔
79
    const list = [] as unknown as DerivedCubicPoints | DerivedQuadPoints;
15,705✔
80
    for (let j = 0; j < c; j += 1) {
15,705✔
81
      list.push({
31,362✔
82
        x: c * (p[j + 1].x - p[j].x),
83
        y: c * (p[j + 1].y - p[j].y),
84
        t: 0,
85
      });
86
    }
87
    dpoints.push(list);
15,705✔
88
    p = list;
15,705✔
89
  }
90
  return dpoints;
5,251✔
91
};
92

93
/**
94
 * @param points
95
 * @param t
96
 */
97
const computeBezier = (
2✔
98
  points: DerivedQuadPoints | DerivedCubicPoints,
99
  t: number,
100
) => {
101
  // shortcuts
102
  /* istanbul ignore next @preserve */
103
  if (t === 0) {
104
    points[0].t = 0;
105
    return points[0];
106
  }
107

108
  const order = points.length - 1;
126,024✔
109

110
  /* istanbul ignore next @preserve */
111
  if (t === 1) {
112
    points[order].t = 1;
113
    return points[order];
114
  }
115

116
  const mt = 1 - t;
126,024✔
117
  let p = points as typeof points | [
126,024✔
118
    DerivedPoint,
119
    DerivedPoint,
120
    DerivedPoint,
121
    DerivedPoint,
122
  ];
123

124
  // constant?
125
  /* istanbul ignore next @preserve */
126
  if (order === 0) {
127
    points[0].t = t;
128
    return points[0];
129
  }
130

131
  // linear?
132
  /* istanbul ignore else @preserve */
133
  if (order === 1) {
126,024✔
134
    return {
1,152✔
135
      x: mt * p[0].x + t * p[1].x,
136
      y: mt * p[0].y + t * p[1].y,
137
      t,
138
    };
139
  }
140

141
  // quadratic/cubic curve?
142
  const mt2 = mt * mt;
124,872✔
143
  const t2 = t * t;
124,872✔
144
  let a = 0;
124,872✔
145
  let b = 0;
124,872✔
146
  let c = 0;
124,872✔
147
  let d = 0;
124,872✔
148
  /* istanbul ignore else @preserve */
149
  if (order === 2) {
124,872✔
150
    p = [p[0], p[1], p[2], { x: 0, y: 0 } as DerivedPoint];
124,872✔
151
    a = mt2;
124,872✔
152
    b = mt * t * 2;
124,872✔
153
    c = t2;
124,872✔
154
  } else if (order === 3) {
155
    a = mt2 * mt;
156
    b = mt2 * t * 3;
157
    c = mt * t2 * 3;
158
    d = t * t2;
159
  }
160
  return {
124,872✔
161
    x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x,
162
    y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y,
163
    t,
164
  };
165
};
166

167
const calculateBezier = (derivativeFn: DeriveCallback, t: number) => {
2✔
168
  const d = derivativeFn(t);
126,024✔
169
  const l = d.x * d.x + d.y * d.y;
126,024✔
170

171
  return Math.sqrt(l);
126,024✔
172
};
173

174
const bezierLength = (derivativeFn: DeriveCallback) => {
2✔
175
  const z = 0.5;
5,251✔
176
  const len = Tvalues.length;
5,251✔
177

178
  let sum = 0;
5,251✔
179

180
  for (let i = 0, t; i < len; i++) {
5,251✔
181
    t = z * Tvalues[i] + z;
126,024✔
182
    sum += Cvalues[i] * calculateBezier(derivativeFn, t);
126,024✔
183
  }
184
  return z * sum;
5,251✔
185
};
186

187
/**
188
 * Returns the length of CubicBezier / Quad segment.
189
 * @param curve cubic / quad bezier segment
190
 */
191
const getBezierLength = (curve: CubicCoordinates | QuadCoordinates) => {
2✔
192
  const points = [] as unknown as CubicPoints | QuadPoints;
5,251✔
193
  for (let idx = 0, len = curve.length, step = 2; idx < len; idx += step) {
5,251✔
194
    points.push({
20,956✔
195
      x: curve[idx],
196
      y: curve[idx + 1],
197
    });
198
  }
199
  const dpoints = deriveBezier(points);
5,251✔
200
  return bezierLength((t: number) => {
5,251✔
201
    return computeBezier(dpoints[0], t);
126,024✔
202
  });
203
};
204

205
// Precision for consider cubic polynom as quadratic one
206
const CBEZIER_MINMAX_EPSILON = 0.00000001;
2✔
207

208
/**
209
 * Returns the most extreme points in a Quad Bezier segment.
210
 * @param A an array which consist of X/Y values
211
 */
212
// https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
213
const minmaxQ = ([v1, cp, v2]: [number, number, number]) => {
2✔
214
  const min = Math.min(v1, v2);
44✔
215
  const max = Math.max(v1, v2);
44✔
216

217
  /* istanbul ignore next @preserve */
218
  if (cp >= v1 ? v2 >= cp : v2 <= cp) {
219
    // if no extremum in ]0,1[
220
    return [min, max] as PointTuple;
221
  }
222

223
  // check if the extremum E is min or max
224
  const E = (v1 * v2 - cp * cp) / (v1 - 2 * cp + v2);
23✔
225
  return (E < min ? [E, max] : [min, E]) as PointTuple;
23✔
226
};
227

228
/**
229
 * Returns the most extreme points in a Cubic Bezier segment.
230
 * @param A an array which consist of X/Y values
231
 * @see https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
232
 */
233
const minmaxC = ([v1, cp1, cp2, v2]: [number, number, number, number]) => {
2✔
234
  const K = v1 - 3 * cp1 + 3 * cp2 - v2;
32✔
235

236
  // if the polynomial is (almost) quadratic and not cubic
237
  /* istanbul ignore next @preserve */
238
  if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
239
    if (v1 === v2 && v1 === cp1) {
240
      // no curve, point targeting same location
241
      return [v1, v2] as PointTuple;
242
    }
243

244
    return minmaxQ([v1, -0.5 * v1 + 1.5 * cp1, v1 - 3 * cp1 + 3 * cp2]);
245
  }
246

247
  // the reduced discriminant of the derivative
248
  const T = -v1 * cp2 + v1 * v2 - cp1 * cp2 - cp1 * v2 + cp1 * cp1 + cp2 * cp2;
32✔
249

250
  // if the polynomial is monotone in [0,1]
251
  if (T <= 0) {
32✔
252
    return [Math.min(v1, v2), Math.max(v1, v2)] as PointTuple;
12✔
253
  }
254
  const S = Math.sqrt(T);
20✔
255

256
  // potential extrema
257
  let min = Math.min(v1, v2);
20✔
258
  let max = Math.max(v1, v2);
20✔
259

260
  const L = v1 - 2 * cp1 + cp2;
20✔
261
  // check local extrema
262
  for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
20✔
263
    // istanbul ignore next @preserve
264
    if (R > 0 && R < 1) {
265
      // if the extrema is for R in [0,1]
266
      const Q = v1 * (1 - R) * (1 - R) * (1 - R) +
267
        cp1 * 3 * (1 - R) * (1 - R) * R + cp2 * 3 * (1 - R) * R * R +
268
        v2 * R * R * R;
269
      if (Q < min) {
270
        min = Q;
271
      }
272
      if (Q > max) {
273
        max = Q;
274
      }
275
    }
276
  }
277

278
  return [min, max] as PointTuple;
20✔
279
};
280
const bezierTools = {
2✔
281
  bezierLength,
282
  calculateBezier,
283
  CBEZIER_MINMAX_EPSILON,
284
  computeBezier,
285
  Cvalues,
286
  deriveBezier,
287
  getBezierLength,
288
  minmaxC,
289
  minmaxQ,
290
  Tvalues,
291
};
292

293
export {
294
  bezierLength,
295
  bezierTools,
296
  calculateBezier,
297
  CBEZIER_MINMAX_EPSILON,
298
  computeBezier,
299
  Cvalues,
300
  deriveBezier,
301
  getBezierLength,
302
  minmaxC,
303
  minmaxQ,
304
  Tvalues,
305
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc