svg.pathmorphing2.js
Version:
Another plugin for the svg.js library to enable path morphing / animation
703 lines (585 loc) • 28 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("svg.js"), require("svg.point.js"));
else if(typeof define === 'function' && define.amd)
define(["svg", "svg.point"], factory);
else if(typeof exports === 'object')
exports["SVG"] = factory(require("svg.js"), require("svg.point.js"));
else
root["SVG"] = factory(root["SVG"], root["SVG"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_3__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
var SVG = __webpack_require__(1)
, CSP = __webpack_require__(2)
module.exports = SVG
// This method will be removed when the version 2.3.7 of svg.js is released since it will be built-in
SVG.extend(SVG.PathArray, {
// Test if the passed path array use the same path data commands as this path array
equalCommands: function(pathArray) {
var i, il, equalCommands
pathArray = new SVG.PathArray(pathArray)
equalCommands = this.value.length === pathArray.value.length
for(i = 0, il = this.value.length; equalCommands && i < il; i++) {
equalCommands = this.value[i][0] === pathArray.value[i][0]
}
return equalCommands
}
})
// Take two path array that don't have the same commands (which mean that they
// cannot be morphed in one another) and return 2 equivalent path array (meaning
// that they produce the same shape as the passed path array) that have the
// same commands (moveto and curveto)
//
// Algorithm used:
// First, convert every path segment of the two passed paths into equivalent cubic Bezier curves.
// Then, calculate the positions relative to the total length of the path of the endpoint of all those cubic Bezier curves.
// After that, split the Bezier curves of the source at the positions that the destination have that are not common to the source and vice versa.
// Finally, make the source and destination have the same number of subpaths.
SVG.utils.makePathsMorphable = function (sourcePathArray, destinationPathArray) {
var source, sourcePositions, sourcePositionsToSplitAt
, destination, destinationPositions, destinationPositionsToSplitAt
, i, il, j, jl
, s, d
, sourceSubpath, destinationSubpath, lastSegPt
// Convert every path segments into equivalent cubic Bezier curves
source = CSP.cubicSuperPath(sourcePathArray)
destination = CSP.cubicSuperPath(destinationPathArray)
// The positions relative to the total length of the path is calculated for the endpoint of all those cubic bezier curves
sourcePositions = CSP.positions(source)
destinationPositions = CSP.positions(destination)
// Find the positions that the destination have that are not in the source and vice versa
sourcePositionsToSplitAt = []
destinationPositionsToSplitAt = []
i = 0, il = sourcePositions.length
j = 0, jl = destinationPositions.length
while(i < il && j < jl) {
// Test if the two values are equal taking into account the imprecision of floating point number
if (Math.abs(sourcePositions[i] - destinationPositions[j]) < 0.000001) {
i++
j++
} else if(sourcePositions[i] > destinationPositions[j]){
sourcePositionsToSplitAt.push(destinationPositions[j++])
} else {
destinationPositionsToSplitAt.push(sourcePositions[i++])
}
}
// If there are still some destination positions left, they all are not in the source and vice versa
sourcePositionsToSplitAt = sourcePositionsToSplitAt.concat(destinationPositions.slice(j))
destinationPositionsToSplitAt = destinationPositionsToSplitAt.concat(sourcePositions.slice(i))
// Split the source and the destination at the positions they don't have in common
CSP.splitAtPositions(source, sourcePositions, sourcePositionsToSplitAt)
CSP.splitAtPositions(destination, destinationPositions, destinationPositionsToSplitAt)
// Break paths so that corresponding subpaths have an equal number of segments
s = source, source = [], sourceSubpath = s[i = 0]
d = destination, destination = [], destinationSubpath = d[j = 0]
while (sourceSubpath && destinationSubpath) {
// Push REFERENCES to the current subpath arrays in their respective array
source.push(sourceSubpath)
destination.push(destinationSubpath)
il = sourceSubpath.length
jl = destinationSubpath.length
// If the current subpath of the source and the current subpath of the destination don't
// have the same length, that mean that the biggest of the two must be split in two
if(il > jl) {
lastSegPt = sourceSubpath[jl-1]
// Perform the split using splice that change the content of the array by removing elements and returning them in an array
sourceSubpath = sourceSubpath.splice(jl)
sourceSubpath.unshift(lastSegPt) // The last segment point is duplicated since these two segments must be joined together
destinationSubpath = d[++j] // This subpath has been accounted for, past to the next
} else if(il < jl) {
lastSegPt = destinationSubpath[il-1]
destinationSubpath = destinationSubpath.splice(il)
destinationSubpath.unshift(lastSegPt)
sourceSubpath = s[++i]
} else {
sourceSubpath = s[++i]
destinationSubpath = d[++j]
}
}
// Convert in path array and return
return [CSP.uncubicSuperPath(source), CSP.uncubicSuperPath(destination)]
}
SVG.extend(SVG.PathArray, {
// Make path array morphable
morph: function(pathArray) {
var pathsMorphable
this.destination = new SVG.PathArray(pathArray)
if(this.equalCommands(this.destination)) {
this.sourceMorphable = this
this.destinationMorphable = this.destination
} else {
pathsMorphable = SVG.utils.makePathsMorphable(this.value, this.destination)
this.sourceMorphable = pathsMorphable[0]
this.destinationMorphable = pathsMorphable[1]
}
return this
}
// Get morphed path array at given position
, at: function(pos) {
// Make sure a destination, a morphable source and a morphable destination are defined
// Also, when pos is 0, we don't return sourceMorphable since the "real" path (this) may have
// closepath commands which differs in behavior from "manually" closing a path (what sourceMorphable does)
// For more details, see: https://www.w3.org/TR/SVG11/paths.html#PathDataClosePathCommand
if (pos === 0 || !(this.destination && this.sourceMorphable && this.destinationMorphable)) {
return this
} else if(pos === 1) {
// destination is used here for the same reason stated above
return this.destination
} else {
var sourceArray = this.sourceMorphable.value
, destinationArray = this.destinationMorphable.value
, array = [], pathArray = new SVG.PathArray()
, i, il, j, jl
// Animate has specified in the SVG spec
// See: https://www.w3.org/TR/SVG11/paths.html#PathElement
for (i = 0, il = sourceArray.length; i < il; i++) {
array[i] = [sourceArray[i][0]]
for(j=1, jl = sourceArray[i].length; j < jl; j++) {
array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
}
// For the two flags of the elliptical arc command, the SVG spec say:
// Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
// Elliptical arc command as an array followed by corresponding indexes:
// ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
// 0 1 2 3 4 5 6 7
if(array[i][0] === 'A') {
array[i][4] = +(array[i][4] != 0)
array[i][5] = +(array[i][5] != 0)
}
}
// Directly modify the value of a path array, this is done this way for performance
pathArray.value = array
return pathArray
}
}
})
/***/ },
/* 1 */
/***/ function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {
var SVG = __webpack_require__(3)
// This function converts every segment of a path array into equivalent cubic Bezier curves
// and return the results in a 3 dimensional array that have the following hierarchy:
// Cubic super path: [ ]
// Segments: [ ] ...
// Segment points: [SVG.Point, SVG.Point, SVG.Point] ...
//
// A segment point is a point with the two control points that are attached to it:
// [First control point, Point, Second control point]
//
// If the passed path array cannot be converted in a cubic super path, this function return an empty array.
exports.cubicSuperPath = function (pathArray) {
pathArray = new SVG.PathArray(pathArray)
var cubicSP = []
, subpath = null
, subpathStartPt = null
, lastPt = null
, lastCtrlPt = null
, i, il, cmd = null, params, lastCmd
, start, control, end
, arcSegPoints, segPt
for (i = 0, il = pathArray.value.length; i < il; i++) {
lastCmd = cmd
cmd = pathArray.value[i][0]
params = pathArray.value[i].slice(1)
switch (cmd) {
case 'M': // moveto
// Parameters: x y
if (lastPt) {
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
}
subpath = []
cubicSP.push(subpath) // Push a reference to the current subpath array in the cubic super path array
subpathStartPt = new SVG.Point(params)
lastPt = subpathStartPt.clone()
lastCtrlPt = subpathStartPt.clone()
break
case 'L': // lineto
// Parameters: x y
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
lastPt = new SVG.Point(params)
lastCtrlPt = lastPt.clone()
break
case 'H': // horizontal lineto
// Parameters: x
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
lastPt = new SVG.Point(params[0], lastPt.y)
lastCtrlPt = lastPt.clone()
break
case 'V': // vertical lineto
// Parameters: y
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
lastPt = new SVG.Point(lastPt.x, params[0])
lastCtrlPt = lastPt.clone()
break
case 'C': // curveto
// Parameters: x1 y1 x2 y2 x y
subpath.push([lastCtrlPt, lastPt, new SVG.Point(params.slice(0,2))])
lastPt = new SVG.Point(params.slice(4,6))
lastCtrlPt = new SVG.Point(params.slice(2,4))
break
case 'S': // shorthand/smooth curveto
// Parameters: x2 y2 x y
// For this version of curveto, the first control point is the reflection of the second control point on the previous command relative to the current point
// If the previous command is not a curveto command, then the first control point is the same as the current point
if(lastCmd === 'C' || lastCmd === 'S') {
subpath.push([lastCtrlPt, lastPt, lastPt.times(2).minus(lastCtrlPt)])
} else {
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
}
lastPt = new SVG.Point(params.slice(2,4))
lastCtrlPt = new SVG.Point(params.slice(0,2))
break
case 'Q': // quadratic Bezier curveto
// Parameters: x1 y1 x y
// For an explanation of the method used, see: https://pomax.github.io/bezierinfo/#reordering
start = lastPt
control = new SVG.Point(params.slice(0,2))
end = new SVG.Point(params.slice(2,4))
subpath.push([lastCtrlPt, start, start.times(1/3).plus(control.times(2/3))])
lastPt = end
lastCtrlPt = control.times(2/3).plus(end.times(1/3))
break
case 'T': // shorthand/smooth quadratic Bézier curveto
// Parameters: x y
// For this version of quadratic Bézier curveto, the control point is the reflection of the control point on the previous command relative to the current point
// If the previous command is not a quadratic Bézier curveto command, then the control point is the same as the current point
start = lastPt
if(lastCmd === 'Q' || lastCmd === 'T') {
control = start.times(2).minus(control)
} else {
control = start
}
end = new SVG.Point(params.slice(0,2))
subpath.push([lastCtrlPt, start, start.times(1/3).plus(control.times(2/3))])
lastPt = end
lastCtrlPt = control.times(2/3).plus(end.times(1/3))
break
case 'A': // elliptical arc
// Parameters: rx ry x-axis-rotation large-arc-flag sweep-flag x y
arcSegPoints = arcToPath(lastPt, params)
arcSegPoints[0][0] = lastCtrlPt
segPt = arcSegPoints.pop()
lastPt = segPt[1]
lastCtrlPt = segPt[0]
Array.prototype.push.apply(subpath, arcSegPoints)
break
case 'Z': // closepath
// Parameters: none
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
// Close the path only if it is not already closed
if(lastPt.x != subpathStartPt.x && lastPt.y != subpathStartPt.y) {
lastPt = subpathStartPt
lastCtrlPt = subpathStartPt.clone()
} else {
lastPt = null
lastCtrlPt = null
}
break
}
}
// Push final segment point if any
if(lastPt) {
subpath.push([lastCtrlPt, lastPt, lastPt.clone()])
}
return cubicSP
}
// This function convert a cubic super path into a path array
exports.uncubicSuperPath = function (cubicSP) {
var i, il, j, jl, array = [], pathArray = new SVG.PathArray, subpath
for (i = 0, il = cubicSP.length; i < il; i++) {
subpath = cubicSP[i]
if (subpath.length) {
array.push(['M'].concat(subpath[0][1].toArray()))
for (j = 1, jl = subpath.length; j < jl; j++) {
array.push(['C'].concat(subpath[j-1][2].toArray(), subpath[j][0].toArray(), subpath[j][1].toArray()))
}
}
}
// Directly modify the value of a path array, this is done this way for performance
pathArray.value = array
return pathArray
}
// Convert an arc segment into equivalent cubic Bezier curves
// Depending on the arc, up to 4 curves might be used to represent it since a
// curve gives a good approximation for only a quarter of an ellipse
// The curves are returned as an array of segment points:
// [ [SVG.Point, SVG.Point, SVG.Point] ... ]
function arcToPath(lastPt, params) {
// Parameters extraction, handle out-of-range parameters as specified in the SVG spec
// See: https://www.w3.org/TR/SVG11/implnote.html#ArcOutOfRangeParameters
var rx = Math.abs(params[0]), ry = Math.abs(params[1]), xAxisRotation = params[2] % 360
, largeArcFlag = params[3], sweepFlag = params[4], x2 = params[5], y2 = params[6]
, A = lastPt, B = new SVG.Point(x2, y2)
, primedCoord, lambda, mat, k, c, cSquare, t, O, OA, OB, tetaStart, tetaEnd
, deltaTeta, nbSectors, f, arcSegPoints, angle, sinAngle, cosAngle, pt, i, il
// Ensure radii are non-zero
if(rx === 0 || ry === 0 || (A.x === B.x && A.y === B.y)) {
// treat this arc as a straight line segment
return [[A, A.clone(), A.clone()], [B, B.clone(), B.clone()]]
}
// Ensure radii are large enough using the algorithm provided in the SVG spec
// See: https://www.w3.org/TR/SVG11/implnote.html#ArcCorrectionOutOfRangeRadii
primedCoord = A.minus(B).divide(2).transform(new SVG.Matrix().rotate(xAxisRotation))
lambda = (primedCoord.x * primedCoord.x) / (rx * rx) + (primedCoord.y * primedCoord.y) / (ry * ry)
if(lambda > 1) {
lambda = Math.sqrt(lambda)
rx = lambda*rx
ry = lambda*ry
}
// To simplify calculations, we make the arc part of a unit circle (rayon is 1) instead of an ellipse
mat = new SVG.Matrix().rotate(xAxisRotation).scale(1/rx, 1/ry).rotate(-xAxisRotation)
A = A.transform(mat)
B = B.transform(mat)
// Calculate the horizontal and vertical distance between the initial and final point of the arc
k = [B.x-A.x, B.y-A.y]
// Find the length of the chord formed by A and B
cSquare = k[0]*k[0] + k[1]*k[1]
c = Math.sqrt(cSquare)
// Calculate the ratios of the horizontal and vertical distance on the length of the chord
k[0] /= c
k[1] /= c
// Calculate the distance between the circle center and the chord midpoint
// using this formula: t = sqrt(r^2 - c^2 / 4)
// where t is the distance between the cirle center and the chord midpoint,
// r is the rayon of the circle and c is the chord length
// From: http://www.ajdesigner.com/phpcircle/circle_segment_chord_t.php
// Because of the imprecision of floating point numbers, cSquare might end
// up being slightly above 4 which would result in a negative radicand
// To prevent that, a test is made before computing the square root
t = (cSquare < 4) ? Math.sqrt(1 - cSquare/4) : 0
// For most situations, there are actually two different ellipses that
// satisfy the constraints imposed by the points A and B, the radii rx and ry,
// and the xAxisRotation
// When the flags largeArcFlag and sweepFlag are equal, it means that the
// second ellipse is used as a solution
// See: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
if(largeArcFlag === sweepFlag) {
t *= -1
}
// Calculate the coordinates of the center of the circle from the midpoint of the chord
// This is done by multiplying the ratios calculated previously by the distance between
// the circle center and the chord midpoint and using these values to go from the midpoint
// to the center of the circle
// The negative of the vertical distance ratio is used to modify the x coordinate while
// the horizontal distance ratio is used to modify the y coordinate
// That is because the center of the circle is perpendicular to the chord and perpendicular
// lines are negative reciprocals
O = new SVG.Point((B.x+A.x)/2 + t*-k[1], (B.y+A.y)/2 + t*k[0])
// Move the center of the circle at the origin
OA = A.minus(O)
OB = B.minus(O)
// Calculate the start and end angle
tetaStart = Math.acos(OA.x/OA.norm())
if (OA.y < 0) {
tetaStart *= -1
}
tetaEnd = Math.acos(OB.x/OB.norm())
if (OB.y < 0) {
tetaEnd *= -1
}
// If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction,
// make sure that the end angle is above the start angle
if (sweepFlag && tetaStart > tetaEnd) {
tetaEnd += 2*Math.PI
}
// If sweep-flag is '0', then the arc will be drawn in a "negative-angle" direction,
// make sure that the end angle is below the start angle
if (!sweepFlag && tetaStart < tetaEnd) {
tetaEnd -= 2*Math.PI
}
// Find the number of Bezier curves that are required to represent the arc
// A cubic Bezier curve gives a good enough approximation when representing at most a quarter of a circle
nbSectors = Math.ceil(Math.abs(tetaStart-tetaEnd) * 2/Math.PI)
// Calculate the coordinates of the points of all the Bezier curves required to represent the arc
// For an in-depth explanation of this part see: http://pomax.github.io/bezierinfo/#circles_cubic
arcSegPoints = []
angle = tetaStart
deltaTeta = (tetaEnd-tetaStart)/nbSectors
f = 4*Math.tan(deltaTeta/4)/3
for (i = 0; i <= nbSectors; i++) { // The <= is because a Bezier curve have a start and a endpoint
cosAngle = Math.cos(angle)
sinAngle = Math.sin(angle)
pt = O.plus(cosAngle, sinAngle)
arcSegPoints[i] = [pt.plus(+f*sinAngle, -f*cosAngle), pt, pt.plus(-f*sinAngle, +f*cosAngle)]
angle += deltaTeta
}
// Remove the first control point of the first segment point and remove the second control point of the last segment point
// These two control points are not used in the approximation of the arc, that is why they are removed
arcSegPoints[0][0] = arcSegPoints[0][1].clone()
arcSegPoints[arcSegPoints.length-1][2] = arcSegPoints[arcSegPoints.length-1][1].clone()
// Revert the transformation that was applied to make the arc part of a unit circle instead of an ellipse
mat = new SVG.Matrix().rotate(xAxisRotation).scale(rx, ry).rotate(-xAxisRotation)
for (i = 0, il = arcSegPoints.length; i < il; i++) {
arcSegPoints[i][0] = arcSegPoints[i][0].transform(mat)
arcSegPoints[i][1] = arcSegPoints[i][1].transform(mat)
arcSegPoints[i][2] = arcSegPoints[i][2].transform(mat)
}
return arcSegPoints
}
// Use de Casteljau's algorithm to split a cubic Bezier curve
// For a description of the algorithm, see: https://pomax.github.io/bezierinfo/#decasteljau
// Return an array of 3 segment points
function segSplit (segPt1, segPt2, t) {
segPt1 = [segPt1[0].clone(), segPt1[1].clone(), segPt1[2].clone()]
segPt2 = [segPt2[0].clone(), segPt2[1].clone(), segPt2[2].clone()]
var m1 = segPt1[1].morph(segPt1[2]).at(t)
, m2 = segPt1[2].morph(segPt2[0]).at(t)
, m3 = segPt2[0].morph(segPt2[1]).at(t)
, m4 = m1.morph(m2).at(t)
, m5 = m2.morph(m3).at(t)
, m = m4.morph(m5).at(t)
return [[segPt1[0], segPt1[1], m1], [m4, m, m5], [m3, segPt2[1], segPt2[2]]]
}
exports.segSplit = segSplit
// Find the length of a cubic Bezier curve using the built-in method getTotalLength of SVGPathElement
// For more info, see: https://www.w3.org/TR/SVG11/paths.html#InterfaceSVGPathElement
function segLength (segPt1, segPt2) {
var path = document.createElementNS(SVG.ns, "path")
, d = ['M', segPt1[1].toArray(), 'C', segPt1[2].toArray(), segPt2[0].toArray(), segPt2[1].toArray()].join(' ')
path.setAttribute('d', d)
return path.getTotalLength()
}
exports.segLength = segLength
// Find the length of all the cubic Bezier curves of a cubic super path and return
// the results in a 2 dimensional array that have the following hierarchy:
// Cubic super path lengths: [ ]
// Segments lengths: [ ] ...
// Cubic Bezier curves length: Number ...
//
// On the returned array, the property total is set to the sum of all the lengths
exports.lengths = function (cubicSP) {
var total = 0
, subpath, lengths = [], lengthsSubpath, length
, i, il, j, jl
for (i = 0, il = cubicSP.length; i < il; i++) {
subpath = cubicSP[i]
lengthsSubpath = []
lengths[i] = lengthsSubpath // Save a reference to the current subpath lengths array in the cubic super path lengths array
for (j = 1, jl = subpath.length; j < jl; j++) {
length = segLength(subpath[j-1], subpath[j])
lengthsSubpath[j-1] = length
total += length
}
}
lengths.total = total
return lengths
}
// Split a cubic Bezier curve at the given length ratio
// Return an array of 3 segment points
function segSplitAtLengthRatio (segPt1, segPt2, lengthRatio) {
var t = 1.0
, tdiv = t
, currentLength = segLength(segPt1, segPt2)
, targetLength = lengthRatio * currentLength
, diff = currentLength - targetLength
, split = segSplit(segPt1, segPt2, t)
, maxNbLoops = 4096 // For not getting stuck in an infinite loop
while (Math.abs(diff) > 0.001 && maxNbLoops--) {
tdiv /= 2
t += (diff < 0) ? tdiv : -tdiv
split = segSplit(segPt1, segPt2, t)
currentLength = segLength(split[0], split[1])
diff = currentLength - targetLength
}
return split
}
exports.segSplitAtLengthRatio = segSplitAtLengthRatio
// Find the position relative to the total length of the endpoint of all the cubic Bezier curves
// of a cubic super path and return the results in a 1 dimensional array
exports.positions = function (cubicSP) {
var lengths = exports.lengths(cubicSP), total = lengths.total
, pos = 0, positions = []
, i, il, j, jl
for (i = 0, il = lengths.length; i < il; i++) {
for (j = 0, jl = lengths[i].length; j < jl; j++) {
pos += lengths[i][j] / total
positions.push(pos)
}
}
return positions
}
// Split the passed cubic super path at the specified positions and return the results as a new cubic super path
// For performance reasons, the positions of the passed cubic super path must also be provided
exports.splitAtPositions = function (cubicSP, positions, positionsToSplitAt){
var subpath, newSubpath
, accumNbPositions = 0, segPt, lengthRatio, split, pos, prevPos
, i, il, j, jl // indexes on the cubicSP array
, k = 0 // index on the positions array
, l = 0, ll = positionsToSplitAt.length
for (i = 0, il = cubicSP.length; i < il && l < ll; i++) {
subpath = cubicSP[i]
// The positions are only for the endpoints of the cubic Bezier curves, so
// a subpath need at least 2 segment points for a position to be on it
if(subpath.length < 2) {continue}
// Test if there are splits to be performed on the current subpath
if(positionsToSplitAt[l] < positions[accumNbPositions + subpath.length-2]) {
k = accumNbPositions
newSubpath = []
cubicSP[i] = newSubpath // Save a reference to the new current subpath array in the cubic super path array
pos = positions[k-1] || 0
// Recopy the content of the current subpath, performing splits where necessary
newSubpath.push(subpath[0])
for (j = 1, jl = subpath.length; j < jl; j++) {
prevPos = pos
pos = positions[k++]
segPt = subpath[j]
while(l < ll && positionsToSplitAt[l] < pos) {
lengthRatio = (positionsToSplitAt[l] - prevPos) / (pos - prevPos)
split = segSplitAtLengthRatio(newSubpath[newSubpath.length-1], segPt, lengthRatio)
newSubpath[newSubpath.length-1] = split[0]
newSubpath.push(split[1])
segPt = split[2]
prevPos = positionsToSplitAt[l++]
}
newSubpath.push(segPt)
}
}
// -1 because positions are only for endpoints of Bezier curves
accumNbPositions += subpath.length - 1
}
}
/***/ },
/* 3 */
/***/ function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
/***/ }
/******/ ])
});
;