High-performance vector path library with length parameterization, projection, and Bezier curve support
npm install @technosyr/path2dfbash
npm install @technosyr/path2df
`
Quick Start
`ts
import { VectorPath } from '@technosyr/path2df';
// Create path (API similar to Canvas)
const path = new VectorPath();
path
.moveTo(0, 0)
.lineTo(100, 0)
.bezierCurveTo(150, 0, 150, 50, 150, 100)
.closePath();
// Length is computed automatically
console.log(path.totalLength); // e.g., 250.5
// Get point at 50% of the path
const midPoint = path.pointAtF(0.5);
console.log(midPoint); // { x: 125, y: 25 }
// Get tangent angle at 75%
const angle = path.angleAtF(0.75);
console.log(angle); // in radians
// Project external point onto path
const proj = path.project({ x: 120, y: 30 });
console.log(proj);
// { f: 0.65, distance: 5.2, point: { x: 118, y: 28 } }
// Render to Canvas
const ctx = canvas.getContext('2d');
ctx.stroke(path.getPath2D());
`
API
$3
#### Path Construction (Canvas-compatible)
`ts
moveTo(x: number, y: number): this
lineTo(x: number, y: number): this
quadraticCurveTo(cx: number, cy: number, x: number, y: number): this
bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): this
closePath(): this
clear(): this
`
#### SVG Path Commands
`ts
horizontalLineTo(x: number): this
// Horizontal line to (x, currentY) — SVG H command
verticalLineTo(y: number): this
// Vertical line to (currentX, y) — SVG V command
smoothCubicTo(cx2: number, cy2: number, x: number, y: number): this
// Smooth cubic Bezier with automatic first control point — SVG S command
smoothQuadraticTo(x: number, y: number): this
// Smooth quadratic Bezier with automatic control point — SVG T command
arcTo(rx: number, ry: number, rotation: number, largeArc: 0 | 1, sweep: 0 | 1, x: number, y: number): this
// Elliptical arc — SVG A command
`
#### Helper Methods
`ts
getCurrentPoint(): Vec2 | null
// Get current pen position
getLastControlPoint(): Vec2 | null
// Get last control point (for smooth curves)
`
#### Properties
`ts
totalLength: number // Total path length (computed on-the-fly)
bounds: Bounds // AABB { minX, minY, maxX, maxY }
segmentCount: number // Number of segments
`
#### Parameterization by Length
`ts
pointAtF(f: number): Vec2
// Get point at normalized position f ∈ [0, 1]
tangentAtF(f: number): Vec2
// Get tangent vector (not normalized) at position f
angleAtF(f: number): number
// Get tangent angle in radians at position f
segmentAtF(f: number): { segment: ISegment; localT: number; index: number } | null
// Find which segment contains position f
`
#### Projection
`ts
project(p: Vec2): { f: number; distance: number; point: Vec2 }
// Project point p onto the path
// Returns: normalized position, distance, and closest point
`
#### Rendering
`ts
getPath2D(): Path2D
// Get cached Path2D for Canvas rendering
`
#### Utilities
`ts
getSegments(): readonly ISegment[]
// Get all segments
samplePoints(step: number): Generator
// Generate points along the path with given pixel step
`
$3
You can also use segments directly:
`ts
import { LineSegment, QuadraticSegment, CubicSegment } from '@technosyr/path2df/segments';
const line = new LineSegment(
{ x: 0, y: 0 },
{ x: 100, y: 100 }
);
console.log(line.length); // 141.42...
console.log(line.pointAt(0.5)); // { x: 50, y: 50 }
console.log(line.tangentAt(0.5)); // { x: 100, y: 100 }
const projection = line.project({ x: 60, y: 40 });
console.log(projection);
// { t: 0.5, d2: 200 } // t = local parameter, d2 = distance²
`
Types
`ts
interface Vec2 {
x: number;
y: number;
}
interface Bounds {
minX: number;
minY: number;
maxX: number;
maxY: number;
}
interface ISegment {
readonly length: number;
readonly start: Vec2;
readonly end: Vec2;
readonly bounds: Bounds;
pointAt(t: number): Vec2;
tangentAt(t: number): Vec2;
project(p: Vec2): { t: number; d2: number };
applyToPath(path: Path2D): void;
}
`
Use Cases
$3
`ts
const path = new VectorPath()
.moveTo(0, 0)
.bezierCurveTo(100, 0, 100, 100, 200, 100);
function animate(time: number) {
const f = (time % 2000) / 2000; // 0-1 over 2 seconds
const pos = path.pointAtF(f);
const angle = path.angleAtF(f);
// Position and rotate sprite
sprite.x = pos.x;
sprite.y = pos.y;
sprite.rotation = angle;
}
`
$3
`ts
function onMouseMove(e: MouseEvent) {
const mouse = { x: e.clientX, y: e.clientY };
const proj = path.project(mouse);
if (proj.distance < 10) {
console.log('Near path at', proj.point);
console.log('Progress:', (proj.f * 100).toFixed(1) + '%');
}
}
`
$3
`ts
const markers = [];
for (let f = 0; f <= 1; f += 0.1) {
const pos = path.pointAtF(f);
const angle = path.angleAtF(f);
markers.push({ pos, angle });
}
markers.forEach(({ pos, angle }) => {
drawMarker(ctx, pos.x, pos.y, angle);
});
`
$3
`ts
// Length is computed automatically during path construction
const path = new VectorPath()
.moveTo(0, 0)
.lineTo(100, 0)
.quadraticCurveTo(150, 50, 100, 100);
console.log('Total length:', path.totalLength.toFixed(2) + 'px');
`
Performance
- Segment lengths computed once during construction (no build step needed)
- Path2D cached and reused for rendering
- Projection uses grid search + Newton-Raphson refinement
- No memory allocations in hot paths (pointAtF, tangentAtF)
Browser Support
Requires Path2D` support (all modern browsers).