Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import dolly from "#static/logos/dolly.svg?raw";import { SVGPathData, SVGPathDataTransformer, type SVGCommand } from "svg-pathdata";
export const DOLLY = /\sd="([^"]+)"/.exec(dolly)![1]!;
export interface Point { x: number; y: number;}
export interface Node extends Point { in?: Point; out?: Point;}
export type Contour = Node[];
const EPSILON = 1e-3;
const same = (a: Point, b: Point) => Math.abs(a.x - b.x) < EPSILON && Math.abs(a.y - b.y) < EPSILON;
const normalize = (d: string) => new SVGPathData(d) .toAbs() .transform(SVGPathDataTransformer.NORMALIZE_HVZ(false, true, true, false)) .normalizeST() .qtToC() .aToC().commands;
export const parsePath = (d: string): Contour[] => { const contours: Contour[] = []; let contour: Contour = []; const close = () => { const first = contour[0]; const last = contour.at(-1); if (first && last && contour.length > 1 && same(first, last)) { first.in = last.in; contour.pop(); } if (contour.length) contours.push(contour); contour = []; }; for (const command of normalize(d)) { if (command.type === SVGPathData.MOVE_TO) { close(); contour.push({ x: command.x, y: command.y }); } else if (command.type === SVGPathData.LINE_TO) { contour.push({ x: command.x, y: command.y }); } else if (command.type === SVGPathData.CURVE_TO) { contour.at(-1)!.out = { x: command.x1, y: command.y1 }; contour.push({ x: command.x, y: command.y, in: { x: command.x2, y: command.y2 } }); } else if (command.type === SVGPathData.CLOSE_PATH) { close(); } } close(); return contours;};
const segment = (from: Node, to: Node): SVGCommand => from.out && to.in ? { type: SVGPathData.CURVE_TO, relative: false, x1: from.out.x, y1: from.out.y, x2: to.in.x, y2: to.in.y, x: to.x, y: to.y } : { type: SVGPathData.LINE_TO, relative: false, x: to.x, y: to.y };
export const toPath = (contours: Contour[]) => new SVGPathData( contours.flatMap((contour): SVGCommand[] => [ { type: SVGPathData.MOVE_TO, relative: false, x: contour[0]!.x, y: contour[0]!.y }, ...contour.map((node, n) => segment(node, contour[(n + 1) % contour.length]!)), { type: SVGPathData.CLOSE_PATH } ]) ) .round(1e4) .encode();
export const smooth = (node: Node) => { if (!node.in || !node.out) return false; const a = { x: node.in.x - node.x, y: node.in.y - node.y }; const b = { x: node.out.x - node.x, y: node.out.y - node.y }; const la = Math.hypot(a.x, a.y); const lb = Math.hypot(b.x, b.y); return la > 0 && lb > 0 && (a.x * b.x + a.y * b.y) / (la * lb) < -0.98;};
export const across = (anchor: Point, handle: Point, length: number): Point => { const dx = anchor.x - handle.x; const dy = anchor.y - handle.y; const l = Math.hypot(dx, dy) || 1; return { x: anchor.x + (dx / l) * length, y: anchor.y + (dy / l) * length };};