Files
suaveplan/packages/media/fonts/font-convert/src/utils/reverse-winding.ts
2026-05-27 23:47:53 +01:00

56 lines
1.8 KiB
TypeScript

import type { PathCommand } from "../types/typeface.js";
type XYCommand = PathCommand & { x: number; y: number };
/**
* Reverses the winding order of path commands for TrueType/CFF compatibility.
* Three.js FontLoader requires counter-clockwise winding; some fonts ship clockwise.
*/
export function reverseWinding(commands: PathCommand[]): PathCommand[] {
const subpaths: PathCommand[][] = [];
let current: PathCommand[] = [];
for (const cmd of commands) {
if (cmd.type === "m") {
current = [cmd];
subpaths.push(current);
} else if (cmd.type !== "z") {
current.push(cmd);
}
}
const reversed: PathCommand[] = [];
for (const path of subpaths) {
// All non-z path commands carry x/y; cast is safe here
const last = path[path.length - 1] as XYCommand;
reversed.push({ type: "m", x: last.x, y: last.y });
for (let i = path.length - 1; i > 0; i--) {
const cmd = path[i] as PathCommand;
const dest = path[i - 1] as XYCommand;
const out: PathCommand = { type: cmd.type, x: dest.x, y: dest.y };
if (cmd.x2 !== undefined && cmd.y2 !== undefined) {
// Swap control point pairs for cubic bezier reversal.
// x1/y1 are always defined when x2/y2 are present in a valid cubic bezier.
const cubic = cmd as PathCommand & {
x1: number;
y1: number;
};
out.x1 = cmd.x2;
out.y1 = cmd.y2;
out.x2 = cubic.x1;
out.y2 = cubic.y1;
} else if (cmd.x1 !== undefined && cmd.y1 !== undefined) {
out.x1 = cmd.x1;
out.y1 = cmd.y1;
}
reversed.push(out);
}
}
return reversed;
}