import { GRID_COLORS } from '../../core/data/tracingStyles'; /** * cnchar-data 1024×1024 坐标系 */ export const CHAR_BOUNDS = { minX: 0, minY: -124, maxX: 1024, maxY: 900, }; export const CHAR_WIDTH = CHAR_BOUNDS.maxX - CHAR_BOUNDS.minX; export const CHAR_HEIGHT = CHAR_BOUNDS.maxY - CHAR_BOUNDS.minY; export interface ScalingTransform { xOffset: number; yOffset: number; scale: number; } export function getScalingTransform( width: number, height: number, padding: number, ): ScalingTransform { const availableWidth = width - 2 * padding; const availableHeight = height - 2 * padding; const scaleX = availableWidth / CHAR_WIDTH; const scaleY = availableHeight / CHAR_HEIGHT; const scale = Math.min(scaleX, scaleY); const scaledWidth = CHAR_WIDTH * scale; const scaledHeight = CHAR_HEIGHT * scale; const centerX = padding + (availableWidth - scaledWidth) / 2; const centerY = padding + (availableHeight - scaledHeight) / 2; const xOffset = -CHAR_BOUNDS.minX * scale + centerX; const yOffset = -CHAR_BOUNDS.minY * scale + centerY; return { xOffset, yOffset, scale }; } /** * SVG 路径解析与绘制(支持 M/L/Q/C/Z 命令) */ export function drawSvgPath(ctx: RenderingContext, pathD: string) { const commandRegex = /([MLQCZ])([^MLQCZ]*?)(?=[MLQCZ]|$)/gi; const commands: Array<{ cmd: string; coords: string }> = []; let match; while ((match = commandRegex.exec(pathD)) !== null) { commands.push({ cmd: match[1].toUpperCase(), coords: match[2].trim() }); } const parseCoords = (coords: string): number[] => coords .split(/[\s,]+/) .filter((part) => part.trim() !== '') .map((part) => parseFloat(part)); for (const { cmd, coords } of commands) { const v = parseCoords(coords); switch (cmd) { case 'M': if (v.length >= 2) ctx.moveTo(v[0], v[1]); break; case 'L': if (v.length >= 2) ctx.lineTo(v[0], v[1]); break; case 'Q': if (v.length >= 4) ctx.quadraticCurveTo(v[0], v[1], v[2], v[3]); break; case 'C': if (v.length >= 6) { ctx.bezierCurveTo(v[0], v[1], v[2], v[3], v[4], v[5]); } break; case 'Z': ctx.closePath(); break; } } } const GRID_CROSS_DASH = [4, 3]; export interface DrawTianZiGridParams { ctx: RenderingContext; /** 田字格中心 x */ cx: number; /** 田字格中心 y */ cy: number; /** 田字格边长 */ size: number; borderColor?: string; middleLineColor?: string; diagonalColor?: string; /** 是否绘制对角线(X线),默认不绘制 */ showDiagonal?: boolean; } export function drawTianZiGrid({ ctx, cx, cy, size, borderColor = GRID_COLORS.border, middleLineColor = GRID_COLORS.middleLine, diagonalColor = GRID_COLORS.diagonal, showDiagonal = false, }: DrawTianZiGridParams) { const half = size / 2; // 外边框 — 实线 ctx.strokeStyle = borderColor; ctx.lineWidth = 1; ctx.setLineDash([]); ctx.strokeRect(cx - half, cy - half, size, size); // 十字线 — 虚线 ctx.strokeStyle = middleLineColor; ctx.setLineDash(GRID_CROSS_DASH); ctx.beginPath(); ctx.moveTo(cx, cy - half); ctx.lineTo(cx, cy + half); ctx.moveTo(cx - half, cy); ctx.lineTo(cx + half, cy); ctx.stroke(); // 对角线 — 虚线 if (showDiagonal) { ctx.strokeStyle = diagonalColor; ctx.setLineDash(GRID_CROSS_DASH); ctx.beginPath(); ctx.moveTo(cx - half, cy - half); ctx.lineTo(cx + half, cy + half); ctx.moveTo(cx + half, cy - half); ctx.lineTo(cx - half, cy + half); ctx.stroke(); } ctx.setLineDash([]); }