ttrpg-tools/src/plotcutter/layout.ts

96 lines
2.2 KiB
TypeScript
Raw Normal View History

2026-03-15 01:43:25 +08:00
import { contourToSvgPath } from './contour';
/**
*
*/
export interface CardPath {
pageIndex: number;
cardIndex: number;
points: [number, number][];
centerX: number;
centerY: number;
pathD: string;
startPoint: [number, number];
endPoint: [number, number];
}
/**
*
* @param cardPaths
* @param a4Height A4
*/
export function generateTravelPaths(
cardPaths: CardPath[],
a4Height: number
): [number, number][][] {
const travelPaths: [number, number][][] = [];
// 起点:左上角 (0, a4Height) - 注意 SVG 坐标 Y 向下plotter 坐标 Y 向上
const startPoint: [number, number] = [0, a4Height];
if (cardPaths.length === 0) {
return travelPaths;
}
// 从起点到第一张卡的起点
travelPaths.push([startPoint, cardPaths[0].startPoint]);
// 卡片之间的移动
for (let i = 0; i < cardPaths.length - 1; i++) {
const currentEnd = cardPaths[i].endPoint;
const nextStart = cardPaths[i + 1].startPoint;
travelPaths.push([currentEnd, nextStart]);
}
// 从最后一张卡返回起点
travelPaths.push([cardPaths[cardPaths.length - 1].endPoint, startPoint]);
return travelPaths;
}
/**
* SVG path
*/
export function travelPathsToSvg(travelPaths: [number, number][][]): string {
return travelPaths.map(path => contourToSvgPath(path, false)).join(' ');
}
/**
*
*/
export function calculateTotalBounds(cardPaths: CardPath[]): {
minX: number;
minY: number;
maxX: number;
maxY: number;
width: number;
height: number;
} {
if (cardPaths.length === 0) {
return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 };
}
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (const cardPath of cardPaths) {
for (const [x, y] of cardPath.points) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
return {
minX,
minY,
maxX,
maxY,
width: maxX - minX,
height: maxY - minY
};
}