UNPKG

tldraw

Version:

A tiny little drawing editor.

140 lines (139 loc) 4.92 kB
import { Vec, assert, average, precise, toDomPrecision } from "@tldraw/editor"; import { getStrokeOutlineTracks } from "./getStrokeOutlinePoints.mjs"; import { getStrokePoints } from "./getStrokePoints.mjs"; import { setStrokePointRadii } from "./setStrokePointRadii.mjs"; function svgInk(rawInputPoints, options = {}) { const { start = {}, end = {} } = options; const { cap: capStart = true } = start; const { cap: capEnd = true } = end; assert(!start.taper && !end.taper, "cap taper not supported here"); assert(!start.easing && !end.easing, "cap easing not supported here"); assert(capStart && capEnd, "cap must be true"); const points = getStrokePoints(rawInputPoints, options); setStrokePointRadii(points, options); const partitions = partitionAtElbows(points); let svg = ""; for (const partition of partitions) { svg += renderPartition(partition, options); } return svg; } function partitionAtElbows(points) { if (points.length <= 2) return [points]; const result = []; let currentPartition = [points[0]]; let prevV = Vec.Sub(points[1].point, points[0].point).uni(); let nextV; let dpr; let prevPoint, thisPoint, nextPoint; for (let i = 1, n = points.length; i < n - 1; i++) { prevPoint = points[i - 1]; thisPoint = points[i]; nextPoint = points[i + 1]; nextV = Vec.Sub(nextPoint.point, thisPoint.point).uni(); dpr = Vec.Dpr(prevV, nextV); prevV = nextV; if (dpr < -0.8) { const elbowPoint = { ...thisPoint, point: thisPoint.input }; currentPartition.push(elbowPoint); result.push(cleanUpPartition(currentPartition)); currentPartition = [elbowPoint]; continue; } currentPartition.push(thisPoint); if (dpr > 0.7) { continue; } if ((Vec.Dist2(prevPoint.point, thisPoint.point) + Vec.Dist2(thisPoint.point, nextPoint.point)) / ((prevPoint.radius + thisPoint.radius + nextPoint.radius) / 3) ** 2 < 1.5) { currentPartition.push(thisPoint); result.push(cleanUpPartition(currentPartition)); currentPartition = [thisPoint]; continue; } } currentPartition.push(points[points.length - 1]); result.push(cleanUpPartition(currentPartition)); return result; } function cleanUpPartition(partition) { const startPoint = partition[0]; let nextPoint; while (partition.length > 2) { nextPoint = partition[1]; if (Vec.Dist2(startPoint.point, nextPoint.point) < ((startPoint.radius + nextPoint.radius) / 2 * 0.5) ** 2) { partition.splice(1, 1); } else { break; } } const endPoint = partition[partition.length - 1]; let prevPoint; while (partition.length > 2) { prevPoint = partition[partition.length - 2]; if (Vec.Dist2(endPoint.point, prevPoint.point) < ((endPoint.radius + prevPoint.radius) / 2 * 0.5) ** 2) { partition.splice(partition.length - 2, 1); } else { break; } } if (partition.length > 1) { partition[0] = { ...partition[0], vector: Vec.Sub(partition[0].point, partition[1].point).uni() }; partition[partition.length - 1] = { ...partition[partition.length - 1], vector: Vec.Sub( partition[partition.length - 2].point, partition[partition.length - 1].point ).uni() }; } return partition; } function circlePath(cx, cy, r) { return "M " + cx + " " + cy + " m -" + r + ", 0 a " + r + "," + r + " 0 1,1 " + r * 2 + ",0 a " + r + "," + r + " 0 1,1 -" + r * 2 + ",0"; } function renderPartition(strokePoints, options = {}) { if (strokePoints.length === 0) return ""; if (strokePoints.length === 1) { return circlePath(strokePoints[0].point.x, strokePoints[0].point.y, strokePoints[0].radius); } const { left, right } = getStrokeOutlineTracks(strokePoints, options); right.reverse(); let svg = `M${precise(left[0])}T`; for (let i = 1; i < left.length; i++) { svg += average(left[i - 1], left[i]); } { const point = strokePoints[strokePoints.length - 1]; const radius = point.radius; const direction = point.vector.clone().per().neg(); const arcStart = Vec.Add(point.point, Vec.Mul(direction, radius)); const arcEnd = Vec.Add(point.point, Vec.Mul(direction, -radius)); svg += `${precise(arcStart)}A${toDomPrecision(radius)},${toDomPrecision( radius )} 0 0 1 ${precise(arcEnd)}T`; } for (let i = 1; i < right.length; i++) { svg += average(right[i - 1], right[i]); } { const point = strokePoints[0]; const radius = point.radius; const direction = point.vector.clone().per(); const arcStart = Vec.Add(point.point, Vec.Mul(direction, radius)); const arcEnd = Vec.Add(point.point, Vec.Mul(direction, -radius)); svg += `${precise(arcStart)}A${toDomPrecision(radius)},${toDomPrecision( radius )} 0 0 1 ${precise(arcEnd)}Z`; } return svg; } export { svgInk }; //# sourceMappingURL=svgInk.mjs.map