UNPKG

@remotion/paths

Version:

Utilities for working with SVG paths

2,012 lines (1,997 loc) 99.9 kB
// src/cut-instruction.ts var cutLInstruction = ({ instruction, lastPoint, progress }) => { const x = lastPoint.x + (instruction.x - lastPoint.x) * progress; const y = lastPoint.y + (instruction.y - lastPoint.y) * progress; return { type: "L", x, y }; }; function interpolatePoint(pA, pB, factor) { return { x: pA.x + (pB.x - pA.x) * factor, y: pA.y + (pB.y - pA.y) * factor }; } function cutCInstruction({ progress, lastPoint, instruction }) { const u = progress; const p0 = { x: lastPoint.x, y: lastPoint.y }; const p1 = { x: instruction.cp1x, y: instruction.cp1y }; const p2 = { x: instruction.cp2x, y: instruction.cp2y }; const p3 = { x: instruction.x, y: instruction.y }; const p01 = interpolatePoint(p0, p1, u); const p12 = interpolatePoint(p1, p2, u); const p23 = interpolatePoint(p2, p3, u); const p012 = interpolatePoint(p01, p12, u); const p123 = interpolatePoint(p12, p23, u); const p0123 = interpolatePoint(p012, p123, u); return { type: "C", cp1x: p01.x, cp1y: p01.y, cp2x: p012.x, cp2y: p012.y, x: p0123.x, y: p0123.y }; } var cutInstruction = ({ instruction, lastPoint, progress }) => { if (instruction.type === "M") { return instruction; } if (instruction.type === "L") { return cutLInstruction({ instruction, lastPoint, progress }); } if (instruction.type === "C") { return cutCInstruction({ instruction, lastPoint, progress }); } if (instruction.type === "Z") { return instruction; } throw new TypeError(`${instruction.type} is not supported.`); }; // src/helpers/bezier-values.ts var tValues = [ [], [], [ -0.5773502691896257, 0.5773502691896257 ], [ 0, -0.7745966692414834, 0.7745966692414834 ], [ -0.33998104358485626, 0.33998104358485626, -0.8611363115940526, 0.8611363115940526 ], [ 0, -0.5384693101056831, 0.5384693101056831, -0.906179845938664, 0.906179845938664 ], [ 0.6612093864662645, -0.6612093864662645, -0.2386191860831969, 0.2386191860831969, -0.932469514203152, 0.932469514203152 ], [ 0, 0.4058451513773972, -0.4058451513773972, -0.7415311855993945, 0.7415311855993945, -0.9491079123427585, 0.9491079123427585 ], [ -0.1834346424956498, 0.1834346424956498, -0.525532409916329, 0.525532409916329, -0.7966664774136267, 0.7966664774136267, -0.9602898564975363, 0.9602898564975363 ], [ 0, -0.8360311073266358, 0.8360311073266358, -0.9681602395076261, 0.9681602395076261, -0.3242534234038089, 0.3242534234038089, -0.6133714327005904, 0.6133714327005904 ], [ -0.14887433898163122, 0.14887433898163122, -0.4333953941292472, 0.4333953941292472, -0.6794095682990244, 0.6794095682990244, -0.8650633666889845, 0.8650633666889845, -0.9739065285171717, 0.9739065285171717 ], [ 0, -0.26954315595234496, 0.26954315595234496, -0.5190961292068118, 0.5190961292068118, -0.7301520055740494, 0.7301520055740494, -0.8870625997680953, 0.8870625997680953, -0.978228658146057, 0.978228658146057 ], [ -0.1252334085114689, 0.1252334085114689, -0.3678314989981802, 0.3678314989981802, -0.5873179542866175, 0.5873179542866175, -0.7699026741943047, 0.7699026741943047, -0.9041172563704749, 0.9041172563704749, -0.9815606342467192, 0.9815606342467192 ], [ 0, -0.2304583159551348, 0.2304583159551348, -0.44849275103644687, 0.44849275103644687, -0.6423493394403402, 0.6423493394403402, -0.8015780907333099, 0.8015780907333099, -0.9175983992229779, 0.9175983992229779, -0.9841830547185881, 0.9841830547185881 ], [ -0.10805494870734367, 0.10805494870734367, -0.31911236892788974, 0.31911236892788974, -0.5152486363581541, 0.5152486363581541, -0.6872929048116855, 0.6872929048116855, -0.827201315069765, 0.827201315069765, -0.9284348836635735, 0.9284348836635735, -0.9862838086968123, 0.9862838086968123 ], [ 0, -0.20119409399743451, 0.20119409399743451, -0.3941513470775634, 0.3941513470775634, -0.5709721726085388, 0.5709721726085388, -0.7244177313601701, 0.7244177313601701, -0.8482065834104272, 0.8482065834104272, -0.937273392400706, 0.937273392400706, -0.9879925180204854, 0.9879925180204854 ], [ -0.09501250983763744, 0.09501250983763744, -0.2816035507792589, 0.2816035507792589, -0.45801677765722737, 0.45801677765722737, -0.6178762444026438, 0.6178762444026438, -0.755404408355003, 0.755404408355003, -0.8656312023878318, 0.8656312023878318, -0.9445750230732326, 0.9445750230732326, -0.9894009349916499, 0.9894009349916499 ], [ 0, -0.17848418149584785, 0.17848418149584785, -0.3512317634538763, 0.3512317634538763, -0.5126905370864769, 0.5126905370864769, -0.6576711592166907, 0.6576711592166907, -0.7815140038968014, 0.7815140038968014, -0.8802391537269859, 0.8802391537269859, -0.9506755217687678, 0.9506755217687678, -0.9905754753144174, 0.9905754753144174 ], [ -0.0847750130417353, 0.0847750130417353, -0.2518862256915055, 0.2518862256915055, -0.41175116146284263, 0.41175116146284263, -0.5597708310739475, 0.5597708310739475, -0.6916870430603532, 0.6916870430603532, -0.8037049589725231, 0.8037049589725231, -0.8926024664975557, 0.8926024664975557, -0.9558239495713977, 0.9558239495713977, -0.9915651684209309, 0.9915651684209309 ], [ 0, -0.16035864564022537, 0.16035864564022537, -0.31656409996362983, 0.31656409996362983, -0.46457074137596094, 0.46457074137596094, -0.600545304661681, 0.600545304661681, -0.7209661773352294, 0.7209661773352294, -0.8227146565371428, 0.8227146565371428, -0.9031559036148179, 0.9031559036148179, -0.96020815213483, 0.96020815213483, -0.9924068438435844, 0.9924068438435844 ], [ -0.07652652113349734, 0.07652652113349734, -0.22778585114164507, 0.22778585114164507, -0.37370608871541955, 0.37370608871541955, -0.5108670019508271, 0.5108670019508271, -0.636053680726515, 0.636053680726515, -0.7463319064601508, 0.7463319064601508, -0.8391169718222188, 0.8391169718222188, -0.912234428251326, 0.912234428251326, -0.9639719272779138, 0.9639719272779138, -0.9931285991850949, 0.9931285991850949 ], [ 0, -0.1455618541608951, 0.1455618541608951, -0.2880213168024011, 0.2880213168024011, -0.4243421202074388, 0.4243421202074388, -0.5516188358872198, 0.5516188358872198, -0.6671388041974123, 0.6671388041974123, -0.7684399634756779, 0.7684399634756779, -0.8533633645833173, 0.8533633645833173, -0.9200993341504008, 0.9200993341504008, -0.9672268385663063, 0.9672268385663063, -0.9937521706203895, 0.9937521706203895 ], [ -0.06973927331972223, 0.06973927331972223, -0.20786042668822127, 0.20786042668822127, -0.34193582089208424, 0.34193582089208424, -0.469355837986757, 0.469355837986757, -0.5876404035069116, 0.5876404035069116, -0.6944872631866827, 0.6944872631866827, -0.7878168059792081, 0.7878168059792081, -0.8658125777203002, 0.8658125777203002, -0.926956772187174, 0.926956772187174, -0.9700604978354287, 0.9700604978354287, -0.9942945854823992, 0.9942945854823992 ], [ 0, -0.1332568242984661, 0.1332568242984661, -0.26413568097034495, 0.26413568097034495, -0.3903010380302908, 0.3903010380302908, -0.5095014778460075, 0.5095014778460075, -0.6196098757636461, 0.6196098757636461, -0.7186613631319502, 0.7186613631319502, -0.8048884016188399, 0.8048884016188399, -0.8767523582704416, 0.8767523582704416, -0.9329710868260161, 0.9329710868260161, -0.9725424712181152, 0.9725424712181152, -0.9947693349975522, 0.9947693349975522 ], [ -0.06405689286260563, 0.06405689286260563, -0.1911188674736163, 0.1911188674736163, -0.3150426796961634, 0.3150426796961634, -0.4337935076260451, 0.4337935076260451, -0.5454214713888396, 0.5454214713888396, -0.6480936519369755, 0.6480936519369755, -0.7401241915785544, 0.7401241915785544, -0.820001985973903, 0.820001985973903, -0.8864155270044011, 0.8864155270044011, -0.9382745520027328, 0.9382745520027328, -0.9747285559713095, 0.9747285559713095, -0.9951872199970213, 0.9951872199970213 ] ]; var cValues = [ [], [], [1, 1], [ 0.8888888888888888, 0.5555555555555556, 0.5555555555555556 ], [ 0.6521451548625461, 0.6521451548625461, 0.34785484513745385, 0.34785484513745385 ], [ 0.5688888888888889, 0.47862867049936647, 0.47862867049936647, 0.23692688505618908, 0.23692688505618908 ], [ 0.3607615730481386, 0.3607615730481386, 0.46791393457269104, 0.46791393457269104, 0.17132449237917036, 0.17132449237917036 ], [ 0.4179591836734694, 0.3818300505051189, 0.3818300505051189, 0.27970539148927664, 0.27970539148927664, 0.1294849661688697, 0.1294849661688697 ], [ 0.362683783378362, 0.362683783378362, 0.31370664587788727, 0.31370664587788727, 0.22238103445337448, 0.22238103445337448, 0.10122853629037626, 0.10122853629037626 ], [ 0.3302393550012598, 0.1806481606948574, 0.1806481606948574, 0.08127438836157441, 0.08127438836157441, 0.31234707704000286, 0.31234707704000286, 0.26061069640293544, 0.26061069640293544 ], [ 0.29552422471475287, 0.29552422471475287, 0.26926671930999635, 0.26926671930999635, 0.21908636251598204, 0.21908636251598204, 0.1494513491505806, 0.1494513491505806, 0.06667134430868814, 0.06667134430868814 ], [ 0.2729250867779006, 0.26280454451024665, 0.26280454451024665, 0.23319376459199048, 0.23319376459199048, 0.18629021092773426, 0.18629021092773426, 0.1255803694649046, 0.1255803694649046, 0.05566856711617366, 0.05566856711617366 ], [ 0.24914704581340277, 0.24914704581340277, 0.2334925365383548, 0.2334925365383548, 0.20316742672306592, 0.20316742672306592, 0.16007832854334622, 0.16007832854334622, 0.10693932599531843, 0.10693932599531843, 0.04717533638651183, 0.04717533638651183 ], [ 0.2325515532308739, 0.22628318026289723, 0.22628318026289723, 0.2078160475368885, 0.2078160475368885, 0.17814598076194574, 0.17814598076194574, 0.13887351021978725, 0.13887351021978725, 0.09212149983772845, 0.09212149983772845, 0.04048400476531588, 0.04048400476531588 ], [ 0.2152638534631578, 0.2152638534631578, 0.2051984637212956, 0.2051984637212956, 0.18553839747793782, 0.18553839747793782, 0.15720316715819355, 0.15720316715819355, 0.12151857068790319, 0.12151857068790319, 0.08015808715976021, 0.08015808715976021, 0.03511946033175186, 0.03511946033175186 ], [ 0.2025782419255613, 0.19843148532711158, 0.19843148532711158, 0.1861610000155622, 0.1861610000155622, 0.16626920581699392, 0.16626920581699392, 0.13957067792615432, 0.13957067792615432, 0.10715922046717194, 0.10715922046717194, 0.07036604748810812, 0.07036604748810812, 0.03075324199611727, 0.03075324199611727 ], [ 0.1894506104550685, 0.1894506104550685, 0.18260341504492358, 0.18260341504492358, 0.16915651939500254, 0.16915651939500254, 0.14959598881657674, 0.14959598881657674, 0.12462897125553388, 0.12462897125553388, 0.09515851168249279, 0.09515851168249279, 0.062253523938647894, 0.062253523938647894, 0.027152459411754096, 0.027152459411754096 ], [ 0.17944647035620653, 0.17656270536699264, 0.17656270536699264, 0.16800410215645004, 0.16800410215645004, 0.15404576107681028, 0.15404576107681028, 0.13513636846852548, 0.13513636846852548, 0.11188384719340397, 0.11188384719340397, 0.08503614831717918, 0.08503614831717918, 0.0554595293739872, 0.0554595293739872, 0.02414830286854793, 0.02414830286854793 ], [ 0.1691423829631436, 0.1691423829631436, 0.16427648374583273, 0.16427648374583273, 0.15468467512626524, 0.15468467512626524, 0.14064291467065065, 0.14064291467065065, 0.12255520671147846, 0.12255520671147846, 0.10094204410628717, 0.10094204410628717, 0.07642573025488905, 0.07642573025488905, 0.0497145488949698, 0.0497145488949698, 0.02161601352648331, 0.02161601352648331 ], [ 0.1610544498487837, 0.15896884339395434, 0.15896884339395434, 0.15276604206585967, 0.15276604206585967, 0.1426067021736066, 0.1426067021736066, 0.12875396253933621, 0.12875396253933621, 0.11156664554733399, 0.11156664554733399, 0.09149002162245, 0.09149002162245, 0.06904454273764123, 0.06904454273764123, 0.0448142267656996, 0.0448142267656996, 0.019461788229726478, 0.019461788229726478 ], [ 0.15275338713072584, 0.15275338713072584, 0.14917298647260374, 0.14917298647260374, 0.14209610931838204, 0.14209610931838204, 0.13168863844917664, 0.13168863844917664, 0.11819453196151841, 0.11819453196151841, 0.10193011981724044, 0.10193011981724044, 0.08327674157670475, 0.08327674157670475, 0.06267204833410907, 0.06267204833410907, 0.04060142980038694, 0.04060142980038694, 0.017614007139152118, 0.017614007139152118 ], [ 0.14608113364969041, 0.14452440398997005, 0.14452440398997005, 0.13988739479107315, 0.13988739479107315, 0.13226893863333747, 0.13226893863333747, 0.12183141605372853, 0.12183141605372853, 0.10879729916714838, 0.10879729916714838, 0.09344442345603386, 0.09344442345603386, 0.0761001136283793, 0.0761001136283793, 0.057134425426857205, 0.057134425426857205, 0.036953789770852494, 0.036953789770852494, 0.016017228257774335, 0.016017228257774335 ], [ 0.13925187285563198, 0.13925187285563198, 0.13654149834601517, 0.13654149834601517, 0.13117350478706238, 0.13117350478706238, 0.12325237681051242, 0.12325237681051242, 0.11293229608053922, 0.11293229608053922, 0.10041414444288096, 0.10041414444288096, 0.08594160621706773, 0.08594160621706773, 0.06979646842452049, 0.06979646842452049, 0.052293335152683286, 0.052293335152683286, 0.03377490158481415, 0.03377490158481415, 0.0146279952982722, 0.0146279952982722 ], [ 0.13365457218610619, 0.1324620394046966, 0.1324620394046966, 0.12890572218808216, 0.12890572218808216, 0.12304908430672953, 0.12304908430672953, 0.11499664022241136, 0.11499664022241136, 0.10489209146454141, 0.10489209146454141, 0.09291576606003515, 0.09291576606003515, 0.07928141177671895, 0.07928141177671895, 0.06423242140852585, 0.06423242140852585, 0.04803767173108467, 0.04803767173108467, 0.030988005856979445, 0.030988005856979445, 0.013411859487141771, 0.013411859487141771 ], [ 0.12793819534675216, 0.12793819534675216, 0.1258374563468283, 0.1258374563468283, 0.12167047292780339, 0.12167047292780339, 0.1155056680537256, 0.1155056680537256, 0.10744427011596563, 0.10744427011596563, 0.09761865210411388, 0.09761865210411388, 0.08619016153195327, 0.08619016153195327, 0.0733464814110803, 0.0733464814110803, 0.05929858491543678, 0.05929858491543678, 0.04427743881741981, 0.04427743881741981, 0.028531388628933663, 0.028531388628933663, 0.0123412297999872, 0.0123412297999872 ] ]; var binomialCoefficients = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]; // src/helpers/bezier-functions.ts var cubicPoint = (xs, ys, t) => { const x = (1 - t) * (1 - t) * (1 - t) * xs[0] + 3 * (1 - t) * (1 - t) * t * xs[1] + 3 * (1 - t) * t * t * xs[2] + t * t * t * xs[3]; const y = (1 - t) * (1 - t) * (1 - t) * ys[0] + 3 * (1 - t) * (1 - t) * t * ys[1] + 3 * (1 - t) * t * t * ys[2] + t * t * t * ys[3]; return { x, y }; }; var getDerivative = (derivative, t, vs) => { const n = vs.length - 1; let value; if (n === 0) { return 0; } if (derivative === 0) { value = 0; for (let k = 0;k <= n; k++) { value += binomialCoefficients[n][k] * (1 - t) ** (n - k) * t ** k * vs[k]; } return value; } const _vs = new Array(n); for (let k = 0;k < n; k++) { _vs[k] = n * (vs[k + 1] - vs[k]); } return getDerivative(derivative - 1, t, _vs); }; function bFunc(xs, ys, t) { const xbase = getDerivative(1, t, xs); const ybase = getDerivative(1, t, ys); const combined = xbase * xbase + ybase * ybase; return Math.sqrt(combined); } var getCubicArcLength = ({ sx, sy, t }) => { let correctedT; const n = 20; const z = t / 2; let sum = 0; for (let i = 0;i < n; i++) { correctedT = z * tValues[n][i] + z; sum += cValues[n][i] * bFunc(sx, sy, correctedT); } return z * sum; }; var quadraticPoint = (xs, ys, t) => { const x = (1 - t) * (1 - t) * xs[0] + 2 * (1 - t) * t * xs[1] + t * t * xs[2]; const y = (1 - t) * (1 - t) * ys[0] + 2 * (1 - t) * t * ys[1] + t * t * ys[2]; return { x, y }; }; var cubicDerivative = (xs, ys, t) => { const derivative = quadraticPoint([3 * (xs[1] - xs[0]), 3 * (xs[2] - xs[1]), 3 * (xs[3] - xs[2])], [3 * (ys[1] - ys[0]), 3 * (ys[2] - ys[1]), 3 * (ys[3] - ys[2])], t); return derivative; }; var getQuadraticArcLength = (xs, ys, t) => { if (t === undefined) { t = 1; } const ax = xs[0] - 2 * xs[1] + xs[2]; const ay = ys[0] - 2 * ys[1] + ys[2]; const bx = 2 * xs[1] - 2 * xs[0]; const by = 2 * ys[1] - 2 * ys[0]; const A = 4 * (ax * ax + ay * ay); const B = 4 * (ax * bx + ay * by); const C = bx * bx + by * by; if (A === 0) { return t * Math.sqrt((xs[2] - xs[0]) ** 2 + (ys[2] - ys[0]) ** 2); } const b = B / (2 * A); const c = C / A; const u = t + b; const k = c - b * b; const uuk = u * u + k > 0 ? Math.sqrt(u * u + k) : 0; const bbk = b * b + k > 0 ? Math.sqrt(b * b + k) : 0; const term = b + Math.sqrt(b * b + k) === 0 ? 0 : k * Math.log(Math.abs((u + uuk) / (b + bbk))); return Math.sqrt(A) / 2 * (u * uuk - b * bbk + term); }; var quadraticDerivative = (xs, ys, t) => { return { x: (1 - t) * 2 * (xs[1] - xs[0]) + t * 2 * (xs[2] - xs[1]), y: (1 - t) * 2 * (ys[1] - ys[0]) + t * 2 * (ys[2] - ys[1]) }; }; var t2length = ({ length, totalLength, func }) => { let error = 1; let t = length / totalLength; let step = (length - func(t)) / totalLength; let numIterations = 0; while (error > 0.001) { const increasedTLength = func(t + step); const increasedTError = Math.abs(length - increasedTLength) / totalLength; if (increasedTError < error) { error = increasedTError; t += step; } else { const decreasedTLength = func(t - step); const decreasedTError = Math.abs(length - decreasedTLength) / totalLength; if (decreasedTError < error) { error = decreasedTError; t -= step; } else { step /= 2; } } numIterations++; if (numIterations > 500) { break; } } return t; }; // src/helpers/bezier.ts var makeQuadratic = ({ startX, startY, cpx, cpy, x, y }) => { const a = { x: startX, y: startY }; const b = { x: cpx, y: cpy }; const c = { x, y }; const length = getQuadraticArcLength([a.x, b.x, c.x, 0], [a.y, b.y, c.y, 0], 1); const getTotalLength = () => { return length; }; const getPointAtLength = (len) => { const xs = [a.x, b.x, c.x, 0]; const xy = [a.y, b.y, c.y, 0]; const t = t2length({ length: len, totalLength: length, func: (i) => getQuadraticArcLength(xs, xy, i) }); return quadraticPoint(xs, xy, t); }; const getTangentAtLength = (len) => { const xs = [a.x, b.x, c.x, 0]; const xy = [a.y, b.y, c.y, 0]; const t = t2length({ length: len, totalLength: length, func: (i) => getQuadraticArcLength(xs, xy, i) }); const derivative = quadraticDerivative(xs, xy, t); const mdl = Math.sqrt(derivative.x * derivative.x + derivative.y * derivative.y); let tangent; if (mdl > 0) { tangent = { x: derivative.x / mdl, y: derivative.y / mdl }; } else { tangent = { x: 0, y: 0 }; } return tangent; }; const getC = () => { return c; }; return { getPointAtLength, getTangentAtLength, getTotalLength, getC, type: "quadratic-bezier", getD: () => ({ x: 0, y: 0 }) }; }; var makeCubic = ({ startX, startY, cp1x, cp1y, cp2x, cp2y, x, y }) => { const a = { x: startX, y: startY }; const b = { x: cp1x, y: cp1y }; const c = { x: cp2x, y: cp2y }; const d = { x, y }; const length = getCubicArcLength({ sx: [a.x, b.x, c.x, d.x], sy: [a.y, b.y, c.y, d.y], t: 1 }); const getTotalLength = () => { return length; }; const getPointAtLength = (len) => { const sx = [a.x, b.x, c.x, d.x]; const sy = [a.y, b.y, c.y, d.y]; const t = t2length({ length: len, totalLength: length, func: (i) => { return getCubicArcLength({ sx, sy, t: i }); } }); return cubicPoint(sx, sy, t); }; const getTangentAtLength = (len) => { const xs = [a.x, b.x, c.x, d.x]; const xy = [a.y, b.y, c.y, d.y]; const t = t2length({ length: len, totalLength: length, func: (i) => getCubicArcLength({ sx: xs, sy: xy, t: i }) }); const derivative = cubicDerivative(xs, xy, t); const mdl = Math.sqrt(derivative.x * derivative.x + derivative.y * derivative.y); let tangent; if (mdl > 0) { tangent = { x: derivative.x / mdl, y: derivative.y / mdl }; } else { tangent = { x: 0, y: 0 }; } return tangent; }; const getC = () => { return c; }; const getD = () => { return d; }; return { getPointAtLength, getTangentAtLength, getTotalLength, getC, getD, type: "cubic-bezier" }; }; // src/helpers/linear.ts var makeLinearPosition = ({ x0, x1, y0, y1 }) => { return { getTotalLength: () => { return Math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2); }, getPointAtLength: (pos) => { let fraction = pos / Math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2); fraction = Number.isNaN(fraction) ? 1 : fraction; const newDeltaX = (x1 - x0) * fraction; const newDeltaY = (y1 - y0) * fraction; return { x: x0 + newDeltaX, y: y0 + newDeltaY }; }, getTangentAtLength: () => { const module = Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); return { x: (x1 - x0) / module, y: (y1 - y0) / module }; }, type: "linear" }; }; // src/helpers/reduced-analysis.ts var conductAnalysis = (instructions) => { let currentPoint = { x: 0, y: 0 }; let moveStart = { x: 0, y: 0 }; const segments = []; for (let i = 0;i < instructions.length; i++) { const instruction = instructions[i]; if (instruction.type === "M") { currentPoint = { x: instruction.x, y: instruction.y }; moveStart = { x: currentPoint.x, y: currentPoint.y }; segments.push({ startPoint: { x: instruction.x, y: instruction.y }, instructionsAndInfo: [ { instruction, function: null, length: 0, startPoint: currentPoint } ] }); } if (instruction.type === "L") { if (segments.length > 0) { const length = Math.sqrt((currentPoint.x - instruction.x) ** 2 + (currentPoint.y - instruction.y) ** 2); segments[segments.length - 1].instructionsAndInfo.push({ instruction, length, function: makeLinearPosition({ x0: currentPoint.x, x1: instruction.x, y0: currentPoint.y, y1: instruction.y }), startPoint: currentPoint }); } currentPoint = { x: instruction.x, y: instruction.y }; } if (instruction.type === "Z") { if (segments.length > 0) { const length = Math.sqrt((segments[segments.length - 1].startPoint.x - currentPoint.x) ** 2 + (segments[segments.length - 1].startPoint.y - currentPoint.y) ** 2); segments[segments.length - 1].instructionsAndInfo.push({ instruction, function: makeLinearPosition({ x0: currentPoint.x, x1: moveStart.x, y0: currentPoint.y, y1: moveStart.y }), length, startPoint: { ...currentPoint } }); } currentPoint = { x: moveStart.x, y: moveStart.y }; } if (instruction.type === "C") { const curve = makeCubic({ startX: currentPoint.x, startY: currentPoint.y, cp1x: instruction.cp1x, cp1y: instruction.cp1y, cp2x: instruction.cp2x, cp2y: instruction.cp2y, x: instruction.x, y: instruction.y }); const length = curve.getTotalLength(); if (segments.length > 0) { segments[segments.length - 1].instructionsAndInfo.push({ instruction, length, function: curve, startPoint: { ...currentPoint } }); } currentPoint = { x: instruction.x, y: instruction.y }; } } return segments; }; // src/parse-path.ts var length = { a: 7, A: 7, C: 6, c: 6, H: 1, h: 1, L: 2, l: 2, M: 2, m: 2, Q: 4, q: 4, S: 4, s: 4, T: 2, t: 2, V: 1, v: 1, Z: 0, z: 0 }; var chunkExact = (array, instruction) => { const chunks = []; const expectedSize = length[instruction]; if (array.length % expectedSize !== 0) { throw new Error(`Expected number of arguments of SVG instruction "${instruction} ${array.join(" ")}" to be a multiple of ${expectedSize}`); } for (let i = 0;i < array.length; i += expectedSize) { chunks.push(array.slice(i, i + expectedSize)); } return chunks; }; var makeInstructions = (arr, instruction, cb) => { return chunkExact(arr, instruction).map((args) => { return cb(args); }); }; var segmentRegExp = /([astvzqmhlc])([^astvzqmhlc]*)/gi; var numberRegExp = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi; var parseValues = (args, instructionType) => { const numbers = args.match(numberRegExp); if (!numbers) { if (instructionType === "Z" || instructionType === "z") { return []; } throw new Error(`Malformed path data: ${instructionType} was expected to have numbers afterwards`); } const expectedArguments = length[instructionType]; if (numbers.length % expectedArguments !== 0) { throw new Error(`Malformed path data: ${instructionType} was expected to have a multiple of ${expectedArguments} numbers, but got "${instructionType} ${numbers.join(" ")} instead"`); } return numbers.map(Number); }; var parsePath = (path) => { if (!path) { throw new Error("No path provided"); } const segments = path.match(segmentRegExp); if (!segments) { throw new Error(`No path elements found in string ${path}`); } return segments.map((segmentString) => { const command = segmentString.charAt(0); const args = parseValues(segmentString.substring(1), command); if (command === "M" && args.length > 2) { const segmentsArray = []; segmentsArray.push({ type: command, x: args[0], y: args[1] }); segmentsArray.push(...makeInstructions(args.slice(2), "L", (numbers) => ({ type: "L", x: numbers[0], y: numbers[1] }))); return segmentsArray; } if (command === "m" && args.length > 2) { const segmentsArray = []; segmentsArray.push({ type: command, dx: args[0], dy: args[1] }); segmentsArray.push(...makeInstructions(args.slice(2), "l", (numbers) => ({ type: "l", dx: numbers[0], dy: numbers[1] }))); return segmentsArray; } if (command === "Z" || command === "z") { return [ { type: "Z" } ]; } if (command === "A") { return makeInstructions(args, command, (numbers) => ({ type: command, rx: numbers[0], ry: numbers[1], xAxisRotation: numbers[2], largeArcFlag: numbers[3] === 1, sweepFlag: numbers[4] === 1, x: numbers[5], y: numbers[6] })); } if (command === "a") { return makeInstructions(args, command, (numbers) => ({ type: command, rx: numbers[0], ry: numbers[1], xAxisRotation: numbers[2], largeArcFlag: numbers[3] === 1, sweepFlag: numbers[4] === 1, dx: numbers[5], dy: numbers[6] })); } if (command === "C") { return makeInstructions(args, command, (numbers) => ({ type: command, cp1x: numbers[0], cp1y: numbers[1], cp2x: numbers[2], cp2y: numbers[3], x: numbers[4], y: numbers[5] })); } if (command === "c") { return makeInstructions(args, command, (numbers) => ({ type: command, cp1dx: numbers[0], cp1dy: numbers[1], cp2dx: numbers[2], cp2dy: numbers[3], dx: numbers[4], dy: numbers[5] })); } if (command === "S") { return makeInstructions(args, command, (numbers) => ({ type: command, cpx: numbers[0], cpy: numbers[1], x: numbers[2], y: numbers[3] })); } if (command === "s") { return makeInstructions(args, command, (numbers) => ({ type: command, cpdx: numbers[0], cpdy: numbers[1], dx: numbers[2], dy: numbers[3] })); } if (command === "H") { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0] })); } if (command === "h") { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0] })); } if (command === "V") { return makeInstructions(args, command, (numbers) => ({ type: command, y: numbers[0] })); } if (command === "v") { return makeInstructions(args, command, (numbers) => ({ type: command, dy: numbers[0] })); } if (command === "L") { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], y: numbers[1] })); } if (command === "M") { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], y: numbers[1] })); } if (command === "m") { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], dy: numbers[1] })); } if (command === "l") { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], dy: numbers[1] })); } if (command === "Q") { return makeInstructions(args, command, (numbers) => ({ type: command, cpx: numbers[0], cpy: numbers[1], x: numbers[2], y: numbers[3] })); } if (command === "q") { return makeInstructions(args, command, (numbers) => ({ type: command, cpdx: numbers[0], cpdy: numbers[1], dx: numbers[2], dy: numbers[3] })); } if (command === "T") { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], y: numbers[1] })); } if (command === "t") { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], dy: numbers[1] })); } throw new Error(`Invalid path element ${segmentString}`); }, []).flat(1); }; // src/helpers/convert-q-to-c-instruction.ts var convertQToCInstruction = (instruction, startPoint) => { const cp1x = startPoint.x + 2 / 3 * (instruction.cpx - startPoint.x); const cp1y = startPoint.y + 2 / 3 * (instruction.cpy - startPoint.y); const cp2x = instruction.x + 2 / 3 * (instruction.cpx - instruction.x); const cp2y = instruction.y + 2 / 3 * (instruction.cpy - instruction.y); return { type: "C", cp1x, cp1y, cp2x, cp2y, x: instruction.x, y: instruction.y }; }; // src/helpers/iterate.ts var iterateOverSegments = ({ segments, iterate }) => { let x = 0; let y = 0; let initialX = 0; let initialY = 0; let cpX = null; let cpY = null; const newSegments = segments.map((s, i) => { const newSeg = iterate({ segment: s, x, y, prevSegment: segments[i - 1] ?? null, initialX, initialY, cpX, cpY }); switch (s.type) { case "M": initialX = s.x; initialY = s.y; x = s.x; y = s.y; cpX = null; cpY = null; break; case "Q": x = s.x; y = s.y; cpX = s.cpx; cpY = s.cpy; break; case "A": x = s.x; y = s.y; cpX = null; cpY = null; break; case "C": x = s.x; y = s.y; cpX = s.cp2x; cpY = s.cp2y; break; case "S": x = s.x; y = s.y; cpX = s.cpx; cpY = s.cpy; break; case "T": if (cpX !== null && cpY !== null) { cpX = x - (cpX - x); cpY = y - (cpY - y); } x = s.x; y = s.y; break; case "L": x = s.x; y = s.y; cpX = null; cpY = null; break; case "V": y = s.y; cpX = null; cpY = null; break; case "H": x = s.x; cpX = null; cpY = null; break; case "Z": x = initialX; y = initialY; cpX = null; cpY = null; break; default: throw new Error(`Unexpected instruction ${s.type}`); } return newSeg; }); return newSegments.flat(1); }; // src/helpers/remove-a-s-t-curves.ts var TAU = Math.PI * 2; function approximate_unit_arc(theta1, delta_theta) { const alpha = 4 / 3 * Math.tan(delta_theta / 4); const x1 = Math.cos(theta1); const y1 = Math.sin(theta1); const x2 = Math.cos(theta1 + delta_theta); const y2 = Math.sin(theta1 + delta_theta); return [ x1, y1, x1 - y1 * alpha, y1 + x1 * alpha, x2 + y2 * alpha, y2 - x2 * alpha, x2, y2 ]; } function unit_vector_angle(ux, uy, vx, vy) { const sign = ux * vy - uy * vx < 0 ? -1 : 1; let dot = ux * vx + uy * vy; if (dot > 1) { dot = 1; } if (dot < -1) { dot = -1; } return sign * Math.acos(dot); } function get_arc_center({ x1, y1, x2, y2, largeArcFlag, sweepFlag, rx, ry, sin_phi, cos_phi }) { const x1p = cos_phi * (x1 - x2) / 2 + sin_phi * (y1 - y2) / 2; const y1p = -sin_phi * (x1 - x2) / 2 + cos_phi * (y1 - y2) / 2; const rx_sq = rx * rx; const ry_sq = ry * ry; const x1p_sq = x1p * x1p; const y1p_sq = y1p * y1p; let radicant = rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq; if (radicant < 0) { radicant = 0; } radicant /= rx_sq * y1p_sq + ry_sq * x1p_sq; radicant = Math.sqrt(radicant) * (largeArcFlag === sweepFlag ? -1 : 1); const cxp = radicant * rx / ry * y1p; const cyp = radicant * -ry / rx * x1p; const cx = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2; const cy = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2; const v1x = (x1p - cxp) / rx; const v1y = (y1p - cyp) / ry; const v2x = (-x1p - cxp) / rx; const v2y = (-y1p - cyp) / ry; const theta1 = unit_vector_angle(1, 0, v1x, v1y); let delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y); if (sweepFlag === false && delta_theta > 0) { delta_theta -= TAU; } if (sweepFlag === true && delta_theta < 0) { delta_theta += TAU; } return [cx, cy, theta1, delta_theta]; } function arcToCircle({ x1, y1, x2, y2, largeArcFlag, sweepFlag, rx, ry, phi }) { const sin_phi = Math.sin(phi * TAU / 360); const cos_phi = Math.cos(phi * TAU / 360); const x1p = cos_phi * (x1 - x2) / 2 + sin_phi * (y1 - y2) / 2; const y1p = -sin_phi * (x1 - x2) / 2 + cos_phi * (y1 - y2) / 2; if (x1p === 0 && y1p === 0) { return []; } if (rx === 0 || ry === 0) { return []; } rx = Math.abs(rx); ry = Math.abs(ry); const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry); if (lambda > 1) { rx *= Math.sqrt(lambda); ry *= Math.sqrt(lambda); } const cc = get_arc_center({ x1, y1, x2, y2, largeArcFlag, sweepFlag, rx, ry, sin_phi, cos_phi }); const result = []; let theta1 = cc[2]; let delta_theta = cc[3]; const segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1); delta_theta /= segments; for (let i = 0;i < segments; i++) { result.push(approximate_unit_arc(theta1, delta_theta)); theta1 += delta_theta; } return result.map((curve) => { for (let i = 0;i < curve.length; i += 2) { let x = curve[i + 0]; let y = curve[i + 1]; x *= rx; y *= ry; const xp = cos_phi * x - sin_phi * y; const yp = sin_phi * x + cos_phi * y; curve[i + 0] = xp + cc[0]; curve[i + 1] = yp + cc[1]; } return curve; }); } var removeATSHVQInstructions = (segments) => { return iterateOverSegments({ segments, iterate: ({ segment, prevSegment, x, y, cpX, cpY }) => { if (segment.type === "H") { return [{ type: "L", x: segment.x, y }]; } if (segment.type === "V") { return [{ type: "L", x, y: segment.y }]; } if (segment.type === "A") { const nextX = segment.x; const nextY = segment.y; const new_segments = arcToCircle({ x1: x, y1: y, x2: nextX, y2: nextY, largeArcFlag: segment.largeArcFlag, sweepFlag: segment.sweepFlag, rx: segment.rx, ry: segment.ry, phi: segment.xAxisRotation }); if (new_segments.length === 0) { return [ { type: "L", x: segment.x, y: segment.y } ]; } const result = new_segments.map((_s) => { return { type: "C", cp1x: _s[2], cp1y: _s[3], cp2x: _s[4], cp2y: _s[5], x: _s[6], y: _s[7] }; }); return result; } if (segment.type === "T") { let prevControlX = 0; let prevControlY = 0; if (prevSegment && (prevSegment.type === "Q" || prevSegment.type === "T")) { prevControlX = cpX; prevControlY = cpY; } else { prevControlX = x; prevControlY = y; } const vectorX = prevControlX - x; const vectorY = prevControlY - y; const newControlX = x - vectorX; const newControlY = y - vectorY; return [ convertQToCInstruction({ type: "Q", cpx: newControlX, cpy: newControlY, x: segment.x, y: segment.y }, { x, y }) ]; } if (segment.type === "S") { let prevControlX = 0; let prevControlY = 0; if (prevSegment && prevSegment.type === "C") { prevControlX = prevSegment.cp2x; prevControlY = prevSegment.cp2y; } else if (prevSegment && prevSegment.type === "S") { prevControlX = prevSegment.cpx; prevControlY = prevSegment.cpy; } else { prevControlX = x; prevControlY = y; } const vectorX = prevControlX - x; const vectorY = prevControlY - y; const newControlX = x - vectorX; const newControlY = y - vectorY; return [ { type: "C", cp1x: newControlX, cp1y: newControlY, cp2x: segment.cpx, cp2y: segment.cpy, x: segment.x, y: segment.y } ]; } if (segment.type === "Q") { return [convertQToCInstruction(segment, { x, y })]; } return [segment]; } }); }; // src/serialize-instructions.ts var serializeInstruction = (instruction) => { if (instruction.type === "A") { return `A ${instruction.rx} ${instruction.ry} ${instruction.xAxisRotation} ${Number(instruction.largeArcFlag)} ${Number(instruction.sweepFlag)} ${instruction.x} ${instruction.y}`; } if (instruction.type === "a") { return `a ${instruction.rx} ${instruction.ry} ${instruction.xAxisRotation} ${Number(instruction.largeArcFlag)} ${Number(instruction.sweepFlag)} ${instruction.dx} ${instruction.dy}`; } if (instruction.type === "C") { return `C ${instruction.cp1x} ${instruction.cp1y} ${instruction.cp2x} ${instruction.cp2y} ${instruction.x} ${instruction.y}`; } if (instruction.type === "c") { return `c ${instruction.cp1dx} ${instruction.cp1dy} ${instruction.cp2dx} ${instruction.cp2dy} ${instruction.dx} ${instruction.dy}`; } if (instruction.type === "S") { return `S ${instruction.cpx} ${instruction.cpy} ${instruction.x} ${instruction.y}`; } if (instruction.type === "s") { return `s ${instruction.cpdx} ${instruction.cpdy} ${instruction.dx} ${instruction.dy}`; } if (instruction.type === "Q") { return `Q ${instruction.cpx} ${instruction.cpy} ${instruction.x} ${instruction.y}`; } if (instruction.type === "q") { return `q ${instruction.cpdx} ${instruction.cpdy} ${instruction.dx} ${instruction.dy}`; } if (instruction.type === "Z") { return "Z"; } if (instruction.type === "H") { return `H ${instruction.x}`; } if (instruction.type === "h") { return `h ${instruction.dx}`; } if (instruction.type === "V") { return `V ${instruction.y}`; } if (instruction.type === "v") { return `v ${instruction.dy}`; } if (instruction.type === "L") { return `L ${instruction.x} ${instruction.y}`; } if (instruction.type === "l") { return `l ${instruction.dx} ${instruction.dy}`; } if (instruction.type === "M") { return `M ${instruction.x} ${instruction.y}`; } if (instruction.type === "m") { return `m ${instruction.dx} ${instruction.dy}`; } if (instruction.type === "T") { return `T ${instruction.x} ${instruction.y}`; } if (instruction.type === "t") { return `t ${instruction.dx} ${instruction.dy}`; } throw new Error(`Unknown instruction type: ${instruction.type}`); }; var serializeInstructions = (path) => { return path.map((p) => { return serializeInstruction(p); }).join(" "); }; // src/normalize-path.ts var normalizeInstructions = (instructions) => { const normalized = []; let x = 0; let y = 0; let moveX = 0; let moveY = 0; for (let i = 0;i < instructions.length; i++) { const instruction = instructions[i]; if (instruction.type === "M") { moveX = instruction.x; moveY = instruction.y; } else if (instruction.type === "m") { moveX += instruction.dx; moveY += instruction.dy; } if (instruction.type === "A" || instruction.type === "C" || instruction.type === "L" || instruction.type === "M" || instruction.type === "Q" || instruction.type === "S" || instruction.type === "T") { normalized.push(instruction); x = instruction.x; y = instruction.y; continue; } if (instruction.type === "a" || instruction.type === "c" || instruction.type === "l" || instruction.type === "m" || instruction.type === "q" || instruction.type === "s" || instruction.type === "t") { const currentX = x; const currentY = y; x += instruction.dx; y += instruction.dy; if (instruction.type === "a") { normalized.push({ type: "A", largeArcFlag: instruction.largeArcFlag, rx: instruction.rx, ry: instruction.ry, sweepFlag: instruction.sweepFlag, xAxisRotation: instruction.xAxisRotation, x, y }); continue; } if (instruction.type === "c") { normalized.push({ type: "C", cp1x: instruction.cp1dx + currentX, cp1y: instruction.cp1dy + currentY, cp2x: instruction.cp2dx + currentX, cp2y: instruction.cp2dy + currentY, x, y }); continue; } if (instruction.type === "l") { normalized.push({ type: "L", x, y }); continue; } if (instruction.type === "m") { normalized.push({ type: "M", x, y }); continue; } if (instruction.type === "q") { normalized.push({ type: "Q", cpx: instruction.cpdx + currentX, cpy: instruction.cpdy + currentY, x, y }); continue; } if (instruction.type === "s") { normalized.push({ type: "S", cpx: instruction.cpdx + currentX, cpy: instruction.cpdy + currentY, x, y }); continue; } if (instruction.type === "t") { normalized.push({ type: "T", x, y }); continue; } } if (instruction.type === "H") { normalized.push(instruction); x = instruction.x; continue; } if (instruction.type === "V") { normalized.push(instruction); y = instruction.y; continue; } if (instruction.type === "Z") { normalized.push(instruction); x = moveX; y = moveY; continue; } if (instruction.type === "h") { x += instruction.dx; normalized.push({ type: "H", x }); continue; } if (instruction.type === "v") { y += instruction.dy; normalized.push({ type: "V", y }); continue; } throw new Error("Unknown instruction type: " + instruction.type); } return normalized; }; var normalizePath = (path) => { const instructions = parsePath(path); const normalized = normalizeInstructions(instructions); return serializeInstructions(normalized); }; // src/reduce-instructions.ts var reduceInstructions = (instruction) => { const simplified = normalizeInstructions(instruction); return removeATSHVQInstructions(simplified); }; // src/cut-path.ts var cutPath = (d, length2) => { const parsed = parsePath(d); const reduced = reduceInstructions(parsed); const constructed = conductAnalysis(reduced); const newInstructions = []; let summedUpLength = 0; for (const segment of constructed) { for (const instructionAndInfo of segment.instructionsAndInfo) { if (summedUpLength + instructionAndInfo.length > length2) { const remainingLength = length2 - summedUpLength; const progress = remainingLength / instructionAndInfo.length; const cut = cutInstruction({ instruction: instructionAndInfo.instruction, lastPoint: instructionAndInfo.startPoint, progress }); newInstructions.push(cut); return serializeInstructions(newInstructions); } summedUpLength += instructionAndInfo.length; newInstructions.push(instructionAndInfo.instruction); if (summedUpLength === length2) { return serializeInstructions(newInstructions); } } } return serializeInstructions(newInstructions); }; // src/debug-path.ts var debugPath = (d) => { const instructions = normalizeInstructions(parsePath(d)); return instructions.map((inst, i) => { if (inst.type === "Z") { return null; } if (inst.type === "H" || inst.type === "V") { return null; } const topLeft = [inst.x - 5, inst.y - 5]; const topRight = [inst.x + 5, inst.y - 5]; const bottomLeft = [inst.x - 5, inst.y + 5]; const bottomRight = [inst.x + 5, inst.y + 5]; const triangle = [ { type: "M", x: topLeft[0], y: topLeft[1] }, { type: "L", x: topRight[0], y: topRight[1] }, { type: "L", x: bottomRight[0], y: bottomRight[1] }, { type: "L", x: bottomLeft[0], y: bottomLeft[1] }, { type: "Z" } ]; return { d: serializeInstructions(triangle), color: i === instructions.length - 1 ? "red" : inst.type === "M" ? "blue" : "green" }; }).filter(Boolean); }; // src/get-bounding-box.ts var CBEZIER_MINMAX_EPSILON = 0.00000001; function minmaxQ(A) { const min = Math.min(A[0], A[2]); const max = Math.max(A[0], A[2]); if (A[1]