/** 控笔图形:100×100 坐标系,Y 轴向下 */ export type PenControlCategory = 'line' | 'curve' | 'shape' | 'combo'; export const PEN_CONTROL_CATEGORY_LABEL: Record = { line: '直线', curve: '曲线', shape: '形状', combo: '组合', }; export type PenControlRenderMode = 'stroke' | 'fill' | 'both'; export interface PenControlPattern { id: string; name: string; category: PenControlCategory; categoryLabel?: string; tags: string[]; paths: string[]; render: PenControlRenderMode; strokeScale?: number; } const RAW_PEN_CONTROL_PATTERNS: PenControlPattern[] = [ { id: 'horizontal-lines', name: '横线', category: 'line', tags: ['基础', '直线'], render: 'stroke', paths: [ 'M 14 26 L 86 26', 'M 14 42 L 86 42', 'M 14 58 L 86 58', 'M 14 74 L 86 74', ], }, { id: 'vertical-lines', name: '竖线', category: 'line', tags: ['基础', '直线'], render: 'stroke', paths: [ 'M 26 14 L 26 86', 'M 42 14 L 42 86', 'M 58 14 L 58 86', 'M 74 14 L 74 86', ], }, { id: 'diagonal-lr', name: '左斜线', category: 'line', tags: ['斜线'], render: 'stroke', paths: [ 'M 14 14 L 86 86', 'M 14 32 L 68 86', 'M 32 14 L 86 68', 'M 14 50 L 50 86', 'M 50 14 L 86 50', ], }, { id: 'diagonal-rl', name: '右斜线', category: 'line', tags: ['斜线'], render: 'stroke', paths: [ 'M 86 14 L 14 86', 'M 86 32 L 32 86', 'M 68 14 L 14 68', 'M 86 50 L 50 86', 'M 50 14 L 14 50', ], }, { id: 'zigzag', name: '锯齿线', category: 'line', tags: ['折线'], render: 'stroke', paths: ['M 14 18 L 26 82 L 38 18 L 50 82 L 62 18 L 74 82 L 86 18'], }, { id: 'loop-single', name: '回字圈', category: 'shape', tags: ['形状'], render: 'stroke', paths: [ 'M 14 14 L 86 14 L 86 86 L 14 86 Z', 'M 28 28 L 72 28 L 72 72 L 28 72 Z', 'M 42 42 L 58 42 L 58 58 L 42 58 Z', ], }, { id: 'spiral', name: '蜗牛线', category: 'combo', tags: ['组合'], render: 'stroke', strokeScale: 0.95, paths: [ 'M 88 50 Q 88 86, 50 86 Q 14 86, 14 50 Q 14 18, 50 18 Q 80 18, 80 50 Q 80 74, 50 74 Q 26 74, 26 50 Q 26 30, 50 30 Q 68 30, 68 50 Q 68 62, 50 62 Q 38 62, 38 50 Q 38 44, 50 44 Q 56 44, 56 50', ], }, { id: 'mountain', name: '山峰线', category: 'line', tags: ['折线'], render: 'stroke', paths: ['M 14 76 L 32 28 L 50 64 L 68 28 L 86 76'], }, { id: 'stair', name: '台阶线', category: 'line', tags: ['折线'], render: 'stroke', paths: [ 'M 14 58 L 14 43 L 38 43 L 38 28 L 62 28 L 62 13 L 86 13', 'M 14 88 L 14 73 L 38 73 L 38 58 L 62 58 L 62 43 L 86 43', ], }, { id: 'corner-turn', name: '转折线', category: 'line', tags: ['折线'], render: 'stroke', paths: [ 'M 86 86 L 14 86 L 14 14 L 86 14 L 86 74 L 26 74 L 26 26 L 74 26 L 74 62 L 38 62 L 38 38 L 62 38 L 62 50', ], }, { id: 'wave', name: '波浪线', category: 'curve', tags: ['曲线'], render: 'stroke', paths: ['M 14 50 Q 32 18, 50 50 Q 68 82, 86 50'], }, { id: 'horizontal-curve', name: '横曲线', category: 'curve', tags: ['曲线'], render: 'stroke', paths: [ 'M 14 24 Q 20 8, 26 24 Q 32 40, 38 24 Q 44 8, 50 24 Q 56 40, 62 24 Q 68 8, 74 24 Q 80 40, 86 24', 'M 14 50 Q 20 34, 26 50 Q 32 66, 38 50 Q 44 34, 50 50 Q 56 66, 62 50 Q 68 34, 74 50 Q 80 66, 86 50', 'M 14 76 Q 20 60, 26 76 Q 32 92, 38 76 Q 44 60, 50 76 Q 56 92, 62 76 Q 68 60, 74 76 Q 80 92, 86 76', ], }, { id: 'vertical-curve', name: '竖曲线', category: 'curve', tags: ['曲线'], render: 'stroke', paths: [ 'M 24 14 Q 8 20, 24 26 Q 40 32, 24 38 Q 8 44, 24 50 Q 40 56, 24 62 Q 8 68, 24 74 Q 40 80, 24 86', 'M 50 14 Q 34 20, 50 26 Q 66 32, 50 38 Q 34 44, 50 50 Q 66 56, 50 62 Q 34 68, 50 74 Q 66 80, 50 86', 'M 76 14 Q 60 20, 76 26 Q 92 32, 76 38 Q 60 44, 76 50 Q 92 56, 76 62 Q 60 68, 76 74 Q 92 80, 76 86', ], }, { id: 's-curve', name: 'S 弯', category: 'curve', tags: ['曲线'], render: 'stroke', strokeScale: 1.1, paths: ['M 80 16 Q 18 18, 50 50 Q 82 82, 20 84'], }, { id: 'arc-up', name: '左弧线', category: 'curve', tags: ['弧线'], render: 'stroke', paths: [ 'M 52 13 Q 13 13, 13 52', 'M 69 30 Q 30 30, 30 69', 'M 86 47 Q 47 47, 47 86', ], }, { id: 'arc-down', name: '右弧线', category: 'curve', tags: ['弧线'], render: 'stroke', paths: [ 'M 13 52 Q 52 52, 52 13', 'M 30 69 Q 69 69, 69 30', 'M 47 86 Q 86 86, 86 47', ], }, { id: 'figure-eight', name: '长8线', category: 'combo', tags: ['组合'], render: 'stroke', strokeScale: 0.95, paths: [ 'M 20 50 C 10 41, 13 20, 27 13 C 41 6, 42 28, 34 40 C 30 45, 25 49, 20 50 C 30 59, 27 80, 14 87 C 5 92, 5 72, 8 60 C 11 55, 15 51, 20 50', 'M 50 50 C 40 41, 43 20, 57 13 C 71 6, 72 28, 64 40 C 60 45, 55 49, 50 50 C 60 59, 57 80, 43 87 C 32 92, 31 72, 36 60 C 40 55, 45 51, 50 50', 'M 80 50 C 70 41, 73 20, 87 13 C 99 7, 100 28, 94 40 C 90 45, 85 49, 80 50 C 90 59, 87 80, 73 87 C 62 92, 61 72, 66 60 C 70 55, 75 51, 80 50', ], }, { id: 'cross-hatch', name: '小太阳', category: 'combo', tags: ['组合'], render: 'stroke', strokeScale: 0.9, paths: [ 'M 62 50 C 62 56.6, 56.6 62, 50 62 C 43.4 62, 38 56.6, 38 50 C 38 43.4, 43.4 38, 50 38 C 56.6 38, 62 43.4, 62 50 Z', 'M 50 30 L 50 12', 'M 50 70 L 50 88', 'M 30 50 L 12 50', 'M 70 50 L 88 50', 'M 36 36 L 22 22', 'M 64 36 L 78 22', 'M 36 64 L 22 78', 'M 64 64 L 78 78', ], }, ]; export const PEN_CONTROL_PATTERNS: PenControlPattern[] = RAW_PEN_CONTROL_PATTERNS.map((p) => ({ ...p, categoryLabel: PEN_CONTROL_CATEGORY_LABEL[p.category], })); const PATTERN_BY_ID = Object.fromEntries( PEN_CONTROL_PATTERNS.map((p) => [p.id, p]), ) as Record; export function getPenControlPattern( id: string, ): PenControlPattern | undefined { return PATTERN_BY_ID[id]; } export const PEN_CONTROL_PATTERN_IDS = PEN_CONTROL_PATTERNS.map((p) => p.id); export function pickRandomPatternIds( count: number, exclude: string[] = [], ): string[] { const pool = PEN_CONTROL_PATTERNS.map((p) => p.id).filter( (id) => !exclude.includes(id), ); const n = Math.max(0, Math.min(count, pool.length)); const shuffled = [...pool].sort(() => Math.random() - 0.5); return shuffled.slice(0, n); }