UNPKG

tldraw

Version:

A tiny little drawing editor.

777 lines (776 loc) • 28 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var PathBuilder_exports = {}; __export(PathBuilder_exports, { PathBuilder: () => PathBuilder, PathBuilderGeometry2d: () => PathBuilderGeometry2d }); module.exports = __toCommonJS(PathBuilder_exports); var import_jsx_runtime = require("react/jsx-runtime"); var import_editor = require("@tldraw/editor"); class PathBuilder { static lineThroughPoints(points, opts) { const path = new PathBuilder(); path.moveTo(points[0].x, points[0].y, { ...opts, offset: opts?.endOffsets ?? opts?.offset }); for (let i = 1; i < points.length; i++) { const isLast = i === points.length - 1; path.lineTo(points[i].x, points[i].y, isLast ? { offset: opts?.endOffsets } : void 0); } return path; } static cubicSplineThroughPoints(points, opts) { const path = new PathBuilder(); const len = points.length; const last = len - 2; const k = 1.25; path.moveTo(points[0].x, points[0].y, { ...opts, offset: opts?.endOffsets ?? opts?.offset }); for (let i = 0; i < len - 1; i++) { const p0 = i === 0 ? points[0] : points[i - 1]; const p1 = points[i]; const p2 = points[i + 1]; const p3 = i === last ? p2 : points[i + 2]; let cp1x, cp1y, cp2x, cp2y; if (i === 0) { cp1x = p0.x; cp1y = p0.y; } else { cp1x = p1.x + (p2.x - p0.x) / 6 * k; cp1y = p1.y + (p2.y - p0.y) / 6 * k; } let pointOpts = void 0; if (i === last) { cp2x = p2.x; cp2y = p2.y; pointOpts = { offset: opts?.endOffsets }; } else { cp2x = p2.x - (p3.x - p1.x) / 6 * k; cp2y = p2.y - (p3.y - p1.y) / 6 * k; } path.cubicBezierTo(p2.x, p2.y, cp1x, cp1y, cp2x, cp2y, pointOpts); } return path; } constructor() { } /** @internal */ commands = []; lastMoveTo = null; assertHasMoveTo() { (0, import_editor.assert)(this.lastMoveTo, "Start an SVGPathBuilder with `.moveTo()`"); return this.lastMoveTo; } moveTo(x, y, opts) { this.lastMoveTo = { type: "move", x, y, closeIdx: null, isClose: false, opts }; this.commands.push(this.lastMoveTo); return this; } lineTo(x, y, opts) { this.assertHasMoveTo(); this.commands.push({ type: "line", x, y, isClose: false, opts }); return this; } circularArcTo(radius, largeArcFlag, sweepFlag, x2, y2, opts) { return this.arcTo(radius, radius, largeArcFlag, sweepFlag, 0, x2, y2, opts); } arcTo(rx, ry, largeArcFlag, sweepFlag, xAxisRotationRadians, x2, y2, opts) { this.assertHasMoveTo(); const x1 = this.commands[this.commands.length - 1].x; const y1 = this.commands[this.commands.length - 1].y; if (x1 === x2 && y1 === y2) { return this; } if (rx === 0 || ry === 0) { return this.lineTo(x2, y2, opts); } const phi = xAxisRotationRadians; const sinPhi = Math.sin(phi); const cosPhi = Math.cos(phi); let rx1 = Math.abs(rx); let ry1 = Math.abs(ry); const dx = (x1 - x2) / 2; const dy = (y1 - y2) / 2; const x1p = cosPhi * dx + sinPhi * dy; const y1p = -sinPhi * dx + cosPhi * dy; const lambda = x1p * x1p / (rx1 * rx1) + y1p * y1p / (ry1 * ry1); if (lambda > 1) { const sqrtLambda = Math.sqrt(lambda); rx1 *= sqrtLambda; ry1 *= sqrtLambda; } const sign = largeArcFlag !== sweepFlag ? 1 : -1; const term = rx1 * rx1 * ry1 * ry1 - rx1 * rx1 * y1p * y1p - ry1 * ry1 * x1p * x1p; const numerator = rx1 * rx1 * y1p * y1p + ry1 * ry1 * x1p * x1p; let radicand = term / numerator; radicand = radicand < 0 ? 0 : radicand; const coef = sign * Math.sqrt(radicand); const cxp = coef * (rx1 * y1p / ry1); const cyp = coef * (-(ry1 * x1p) / rx1); const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2; const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2; const ux = (x1p - cxp) / rx1; const uy = (y1p - cyp) / ry1; const vx = (-x1p - cxp) / rx1; const vy = (-y1p - cyp) / ry1; const startAngle = Math.atan2(uy, ux); let endAngle = Math.atan2(vy, vx); if (!sweepFlag && endAngle > startAngle) { endAngle -= 2 * Math.PI; } else if (sweepFlag && endAngle < startAngle) { endAngle += 2 * Math.PI; } const sweepAngle = endAngle - startAngle; const approximateArcLength = Math.max(rx1, ry1) * Math.abs(sweepAngle); const numSegments = Math.min(4, Math.ceil(Math.abs(sweepAngle) / (Math.PI / 2))); const resolutionPerSegment = Math.ceil( (0, import_editor.getVerticesCountForArcLength)(approximateArcLength) / numSegments ); const anglePerSegment = sweepAngle / numSegments; const ellipsePoint = (angle) => { return { x: cx + rx1 * Math.cos(angle) * cosPhi - ry1 * Math.sin(angle) * sinPhi, y: cy + rx1 * Math.cos(angle) * sinPhi + ry1 * Math.sin(angle) * cosPhi }; }; const ellipseDerivative = (angle) => { return { x: -rx1 * Math.sin(angle) * cosPhi - ry1 * Math.cos(angle) * sinPhi, y: -rx1 * Math.sin(angle) * sinPhi + ry1 * Math.cos(angle) * cosPhi }; }; for (let i = 0; i < numSegments; i++) { const theta1 = startAngle + i * anglePerSegment; const theta2 = startAngle + (i + 1) * anglePerSegment; const deltaTheta = theta2 - theta1; const start = ellipsePoint(theta1); const end = ellipsePoint(theta2); const d1 = ellipseDerivative(theta1); const d2 = ellipseDerivative(theta2); const handleScale = 4 / 3 * Math.tan(deltaTheta / 4); const cp1x = start.x + handleScale * d1.x; const cp1y = start.y + handleScale * d1.y; const cp2x = end.x - handleScale * d2.x; const cp2y = end.y - handleScale * d2.y; const bezierOpts = i === 0 ? opts : { ...opts, mergeWithPrevious: true }; this.cubicBezierToWithResolution( end.x, end.y, cp1x, cp1y, cp2x, cp2y, bezierOpts, resolutionPerSegment ); } return this; } cubicBezierTo(x, y, cp1X, cp1Y, cp2X, cp2Y, opts) { return this.cubicBezierToWithResolution(x, y, cp1X, cp1Y, cp2X, cp2Y, opts); } cubicBezierToWithResolution(x, y, cp1X, cp1Y, cp2X, cp2Y, opts, resolution) { this.assertHasMoveTo(); this.commands.push({ type: "cubic", x, y, cp1: { x: cp1X, y: cp1Y }, cp2: { x: cp2X, y: cp2Y }, isClose: false, opts, resolution }); return this; } close() { const lastMoveTo = this.assertHasMoveTo(); const lastCommand = this.commands[this.commands.length - 1]; if ((0, import_editor.approximately)(lastMoveTo.x, lastCommand.x) && (0, import_editor.approximately)(lastMoveTo.y, lastCommand.y)) { lastCommand.isClose = true; } else { this.commands.push({ type: "line", x: lastMoveTo.x, y: lastMoveTo.y, isClose: true }); } lastMoveTo.closeIdx = this.commands.length - 1; this.lastMoveTo = null; return this; } toD(opts = {}) { const { startIdx = 0, endIdx = this.commands.length, onlyFilled = false } = opts; const parts = []; let isSkippingCurrentLine = false; let didAddMove = false; let didAddNaturalMove = false; const addMoveIfNeeded = (i) => { if (didAddMove || i === 0) return; didAddMove = true; const command = this.commands[i - 1]; parts.push("M", (0, import_editor.toDomPrecision)(command.x), (0, import_editor.toDomPrecision)(command.y)); }; for (let i = startIdx; i < endIdx; i++) { const command = this.commands[i]; switch (command.type) { case "move": { const isFilled = command.opts?.geometry === false ? false : command.opts?.geometry?.isFilled ?? false; if (onlyFilled && !isFilled) { isSkippingCurrentLine = true; } else { isSkippingCurrentLine = false; didAddMove = true; didAddNaturalMove = true; parts.push("M", (0, import_editor.toDomPrecision)(command.x), (0, import_editor.toDomPrecision)(command.y)); } break; } case "line": if (isSkippingCurrentLine) break; addMoveIfNeeded(i); if (command.isClose && didAddNaturalMove) { parts.push("Z"); } else { parts.push("L", (0, import_editor.toDomPrecision)(command.x), (0, import_editor.toDomPrecision)(command.y)); } break; case "cubic": if (isSkippingCurrentLine) break; addMoveIfNeeded(i); parts.push( "C", (0, import_editor.toDomPrecision)(command.cp1.x), (0, import_editor.toDomPrecision)(command.cp1.y), (0, import_editor.toDomPrecision)(command.cp2.x), (0, import_editor.toDomPrecision)(command.cp2.y), (0, import_editor.toDomPrecision)(command.x), (0, import_editor.toDomPrecision)(command.y) ); break; default: (0, import_editor.exhaustiveSwitchError)(command, "type"); } } return parts.join(" "); } toSvg(opts) { if (opts.forceSolid) { return this.toSolidSvg(opts); } switch (opts.style) { case "solid": return this.toSolidSvg(opts); case "dashed": case "dotted": return this.toDashedSvg(opts); case "draw": { const d = this.toDrawSvg(opts); return d; } default: (0, import_editor.exhaustiveSwitchError)(opts, "style"); } } toPath2D(opts) { if (opts.forceSolid || opts.style === "solid") { return new Path2D(this.toD({ onlyFilled: opts.onlyFilled })); } if (opts.style === "draw") { return new Path2D(this.toDrawD(opts)); } return new Path2D(this.toD({ onlyFilled: opts.onlyFilled })); } toGeometry() { const geometries = []; let current = null; for (let i = 0; i < this.commands.length; i++) { const command = this.commands[i]; if (command.type === "move") { if (current && current.opts?.geometry !== false) { geometries.push( new PathBuilderGeometry2d(this, current.startIdx, i, { ...current.opts?.geometry, isFilled: current.opts?.geometry?.isFilled ?? false, isClosed: current.moveCommand.closeIdx !== null }) ); } current = { startIdx: i, moveCommand: command, opts: command.opts, isClosed: false }; } if (command.isClose) { (0, import_editor.assert)(current, "No current move command"); current.isClosed = true; } } if (current && current.opts?.geometry !== false) { geometries.push( new PathBuilderGeometry2d(this, current.startIdx, this.commands.length, { ...current.opts?.geometry, isFilled: current.opts?.geometry?.isFilled ?? false, isClosed: current.moveCommand.closeIdx !== null }) ); } (0, import_editor.assert)(geometries.length > 0); if (geometries.length === 1) return geometries[0]; return new import_editor.Group2d({ children: geometries }); } toSolidSvg(opts) { const { strokeWidth, props } = opts; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeWidth, d: this.toD({ onlyFilled: opts.onlyFilled }), ...props }); } toDashedSvg(opts) { const { style, strokeWidth, snap, lengthRatio, props: { markerStart, markerEnd, ...props } = {} } = opts; const parts = []; let isCurrentPathClosed = false; let isSkippingCurrentLine = false; let currentLineOpts = void 0; let currentRun = null; const addCurrentRun = () => { if (!currentRun) return; const { startIdx, endIdx, isFirst, isLast, length, lineOpts, pathIsClosed } = currentRun; currentRun = null; if (startIdx === endIdx && this.commands[startIdx].type === "move") return; const start = lineOpts?.dashStart ?? opts.start; const end = lineOpts?.dashEnd ?? opts.end; const { strokeDasharray, strokeDashoffset } = (0, import_editor.getPerfectDashProps)(length, strokeWidth, { style, snap, lengthRatio, start: isFirst ? start ?? (pathIsClosed ? "outset" : "none") : "outset", end: isLast ? end ?? (pathIsClosed ? "outset" : "none") : "outset" }); const d = this.toD({ startIdx, endIdx: endIdx + 1 }); parts.push( /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "path", { d, strokeDasharray, strokeDashoffset, markerStart: isFirst ? markerStart : void 0, markerEnd: isLast ? markerEnd : void 0 }, parts.length ) ); }; for (let i = 0; i < this.commands.length; i++) { const command = this.commands[i]; const lastCommand = this.commands[i - 1]; if (command.type === "move") { isCurrentPathClosed = command.closeIdx !== null; const isFilled = command.opts?.geometry === false ? false : command.opts?.geometry?.isFilled ?? false; if (opts.onlyFilled && !isFilled) { isSkippingCurrentLine = true; } else { isSkippingCurrentLine = false; currentLineOpts = command.opts; } continue; } if (isSkippingCurrentLine) continue; const segmentLength = this.calculateSegmentLength(lastCommand, command); const isFirst = lastCommand.type === "move"; const isLast = command.isClose || i === this.commands.length - 1 || this.commands[i + 1]?.type === "move"; if (currentRun && command.opts?.mergeWithPrevious) { currentRun.length += segmentLength; currentRun.endIdx = i; currentRun.isLast = isLast; } else { addCurrentRun(); currentRun = { startIdx: i, endIdx: i, isFirst, isLast, length: segmentLength, lineOpts: currentLineOpts, pathIsClosed: isCurrentPathClosed }; } } addCurrentRun(); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { strokeWidth, ...props, children: parts }); } toDrawSvg(opts) { return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeWidth: opts.strokeWidth, d: this.toDrawD(opts), ...opts.props }); } toDrawD(opts) { const { strokeWidth, randomSeed, offset: defaultOffset = strokeWidth / 3, roundness: defaultRoundness = strokeWidth * 2, passes = 2, onlyFilled = false } = opts; const parts = []; const commandInfo = this.getCommandInfo(); const drawCommands = []; let lastMoveCommandIdx = null; for (let i = 0; i < this.commands.length; i++) { const command = this.commands[i]; const offset = command.opts?.offset ?? defaultOffset; const roundness = command.opts?.roundness ?? defaultRoundness; if (command.type === "move") { lastMoveCommandIdx = i; } const nextIdx = command.isClose ? (0, import_editor.assertExists)(lastMoveCommandIdx) + 1 : !this.commands[i + 1] || this.commands[i + 1].type === "move" ? void 0 : i + 1; const nextInfo = nextIdx !== void 0 && this.commands[nextIdx] && this.commands[nextIdx]?.type !== "move" ? commandInfo[nextIdx] : void 0; const currentSupportsRoundness = commandsSupportingRoundness[command.type]; const nextSupportsRoundness = nextIdx !== void 0 ? commandsSupportingRoundness[this.commands[nextIdx].type] : false; const currentInfo = commandInfo[i]; const tangentToPrev = currentInfo?.tangentEnd; const tangentToNext = nextInfo?.tangentStart; const roundnessClampedForAngle = currentSupportsRoundness && nextSupportsRoundness && tangentToPrev && tangentToNext && import_editor.Vec.Len2(tangentToPrev) > 0.01 && import_editor.Vec.Len2(tangentToNext) > 0.01 ? (0, import_editor.modulate)( Math.abs(import_editor.Vec.AngleBetween(tangentToPrev, tangentToNext)), [Math.PI / 2, Math.PI], [roundness, 0], true ) : 0; const shortestDistance = Math.min( currentInfo?.length ?? Infinity, nextInfo?.length ?? Infinity ); const offsetLimit = shortestDistance - roundnessClampedForAngle * 2; const offsetAmount = (0, import_editor.clamp)(offset, 0, offsetLimit / 4); const roundnessBeforeClampedForLength = Math.min( roundnessClampedForAngle, (currentInfo?.length ?? Infinity) / 4 ); const roundnessAfterClampedForLength = Math.min( roundnessClampedForAngle, (nextInfo?.length ?? Infinity) / 4 ); const drawCommand = { command, offsetAmount, roundnessBefore: roundnessBeforeClampedForLength, roundnessAfter: roundnessAfterClampedForLength, tangentToPrev: commandInfo[i]?.tangentEnd, tangentToNext: nextInfo?.tangentStart, moveDidClose: false }; drawCommands.push(drawCommand); if (command.isClose && lastMoveCommandIdx !== null) { const lastMoveCommand = drawCommands[lastMoveCommandIdx]; lastMoveCommand.moveDidClose = true; lastMoveCommand.roundnessAfter = roundnessAfterClampedForLength; } else if (command.type === "move") { lastMoveCommandIdx = i; } } for (let pass = 0; pass < passes; pass++) { const random = (0, import_editor.rng)(randomSeed + pass); let lastMoveToOffset = { x: 0, y: 0 }; let isSkippingCurrentLine = false; for (const { command, offsetAmount, roundnessBefore, roundnessAfter, tangentToNext, tangentToPrev } of drawCommands) { const offset = command.isClose ? lastMoveToOffset : { x: random() * offsetAmount, y: random() * offsetAmount }; if (command.type === "move") { lastMoveToOffset = offset; const isFilled = command.opts?.geometry === false ? false : command.opts?.geometry?.isFilled ?? false; if (onlyFilled && !isFilled) { isSkippingCurrentLine = true; } else { isSkippingCurrentLine = false; } } if (isSkippingCurrentLine) continue; const offsetPoint = import_editor.Vec.Add(command, offset); const endPoint = tangentToNext && roundnessAfter > 0 ? import_editor.Vec.Mul(tangentToNext, -roundnessAfter).add(offsetPoint) : offsetPoint; const startPoint = tangentToPrev && roundnessBefore > 0 ? import_editor.Vec.Mul(tangentToPrev, roundnessBefore).add(offsetPoint) : offsetPoint; if (endPoint === offsetPoint || startPoint === offsetPoint) { switch (command.type) { case "move": parts.push("M", (0, import_editor.toDomPrecision)(endPoint.x), (0, import_editor.toDomPrecision)(endPoint.y)); break; case "line": parts.push("L", (0, import_editor.toDomPrecision)(endPoint.x), (0, import_editor.toDomPrecision)(endPoint.y)); break; case "cubic": { const offsetCp1 = import_editor.Vec.Add(command.cp1, offset); const offsetCp2 = import_editor.Vec.Add(command.cp2, offset); parts.push( "C", (0, import_editor.toDomPrecision)(offsetCp1.x), (0, import_editor.toDomPrecision)(offsetCp1.y), (0, import_editor.toDomPrecision)(offsetCp2.x), (0, import_editor.toDomPrecision)(offsetCp2.y), (0, import_editor.toDomPrecision)(endPoint.x), (0, import_editor.toDomPrecision)(endPoint.y) ); break; } default: (0, import_editor.exhaustiveSwitchError)(command, "type"); } } else { switch (command.type) { case "move": parts.push("M", (0, import_editor.toDomPrecision)(endPoint.x), (0, import_editor.toDomPrecision)(endPoint.y)); break; case "line": parts.push( "L", (0, import_editor.toDomPrecision)(startPoint.x), (0, import_editor.toDomPrecision)(startPoint.y), "Q", (0, import_editor.toDomPrecision)(offsetPoint.x), (0, import_editor.toDomPrecision)(offsetPoint.y), (0, import_editor.toDomPrecision)(endPoint.x), (0, import_editor.toDomPrecision)(endPoint.y) ); break; case "cubic": { const offsetCp1 = import_editor.Vec.Add(command.cp1, offset); const offsetCp2 = import_editor.Vec.Add(command.cp2, offset); parts.push( "C", (0, import_editor.toDomPrecision)(offsetCp1.x), (0, import_editor.toDomPrecision)(offsetCp1.y), (0, import_editor.toDomPrecision)(offsetCp2.x), (0, import_editor.toDomPrecision)(offsetCp2.y), (0, import_editor.toDomPrecision)(offsetPoint.x), (0, import_editor.toDomPrecision)(offsetPoint.y) ); break; } default: (0, import_editor.exhaustiveSwitchError)(command, "type"); } } } } return parts.join(" "); } calculateSegmentLength(lastPoint, command) { switch (command.type) { case "move": return 0; case "line": return import_editor.Vec.Dist(lastPoint, command); case "cubic": return CubicBezier.length( lastPoint.x, lastPoint.y, command.cp1.x, command.cp1.y, command.cp2.x, command.cp2.y, command.x, command.y ); default: (0, import_editor.exhaustiveSwitchError)(command, "type"); } } /** @internal */ getCommands() { return this.commands; } /** @internal */ getCommandInfo() { const commandInfo = []; for (let i = 1; i < this.commands.length; i++) { const previous = this.commands[i - 1]; const current = this.commands[i]; if (current._info) { commandInfo[i] = current._info; continue; } if (current.type === "move") { continue; } let tangentStart, tangentEnd; switch (current.type) { case "line": tangentStart = tangentEnd = import_editor.Vec.Sub(previous, current).uni(); break; case "cubic": { tangentStart = import_editor.Vec.Sub(current.cp1, previous).uni(); tangentEnd = import_editor.Vec.Sub(current.cp2, current).uni(); break; } default: (0, import_editor.exhaustiveSwitchError)(current, "type"); } current._info = { tangentStart, tangentEnd, length: this.calculateSegmentLength(previous, current) }; commandInfo[i] = current._info; } return commandInfo; } } const commandsSupportingRoundness = { line: true, move: true, cubic: false }; class PathBuilderGeometry2d extends import_editor.Geometry2d { constructor(path, startIdx, endIdx, options) { super(options); this.path = path; this.startIdx = startIdx; this.endIdx = endIdx; } _segments = null; getSegments() { if (this._segments) return this._segments; this._segments = []; let last = this.path.commands[this.startIdx]; (0, import_editor.assert)(last.type === "move"); for (let i = this.startIdx + 1; i < this.endIdx; i++) { const command = this.path.commands[i]; (0, import_editor.assert)(command.type !== "move"); switch (command.type) { case "line": this._segments.push(new import_editor.Edge2d({ start: import_editor.Vec.From(last), end: import_editor.Vec.From(command) })); break; case "cubic": { this._segments.push( new import_editor.CubicBezier2d({ start: import_editor.Vec.From(last), cp1: import_editor.Vec.From(command.cp1), cp2: import_editor.Vec.From(command.cp2), end: import_editor.Vec.From(command), resolution: command.resolution }) ); break; } default: (0, import_editor.exhaustiveSwitchError)(command, "type"); } last = command; } return this._segments; } getVertices(filters) { const vs = this.getSegments().flatMap((s) => s.getVertices(filters)).filter((vertex, i, vertices) => { const prev = vertices[i - 1]; if (!prev) return true; return !import_editor.Vec.Equals(prev, vertex); }); if (this.isClosed) { const last = vs[vs.length - 1]; const first = vs[0]; if (!import_editor.Vec.Equals(last, first)) { vs.push(first); } } return vs; } nearestPoint(point, _filters) { let nearest = null; let nearestDistance = Infinity; for (const segment of this.getSegments()) { const candidate = segment.nearestPoint(point); const distance = import_editor.Vec.Dist2(point, candidate); if (distance < nearestDistance) { nearestDistance = distance; nearest = candidate; } } (0, import_editor.assert)(nearest, "No nearest point found"); return nearest; } hitTestLineSegment(A, B, distance = 0, filters) { return super.hitTestLineSegment(A, B, distance, filters); } getSvgPathData() { return this.path.toD({ startIdx: this.startIdx, endIdx: this.endIdx }); } } /*! * Adapted from https://github.com/adobe-webplatform/Snap.svg/tree/master * Apache License: https://github.com/adobe-webplatform/Snap.svg/blob/master/LICENSE * https://github.com/adobe-webplatform/Snap.svg/blob/c8e483c9694517e24b282f8f59f985629f4994ce/dist/snap.svg.js#L5786 */ const CubicBezier = { base3(t, p1, p2, p3, p4) { const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4; const t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; return t * t2 - 3 * p1 + 3 * p2; }, /** * Calculate the approximate length of a cubic bezier curve from (x1, y1) to (x4, y4) with * control points (x2, y2) and (x3, y3). */ length(x1, y1, x2, y2, x3, y3, x4, y4, z = 1) { z = z > 1 ? 1 : z < 0 ? 0 : z; const z2 = z / 2; const n = 12; let sum = 0; sum = 0; for (let i = 0; i < n; i++) { const ct = z2 * CubicBezier.Tvalues[i] + z2; const xbase = CubicBezier.base3(ct, x1, x2, x3, x4); const ybase = CubicBezier.base3(ct, y1, y2, y3, y4); const comb = xbase * xbase + ybase * ybase; sum += CubicBezier.Cvalues[i] * Math.sqrt(comb); } return z2 * sum; }, Tvalues: [ -0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, -0.9041, 0.9041, -0.9816, 0.9816 ], Cvalues: [ 0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, 0.1069, 0.0472, 0.0472 ] }; //# sourceMappingURL=PathBuilder.js.map