feat: 添加控笔页

This commit is contained in:
R524809
2026-05-28 16:46:30 +08:00
parent 824e83c887
commit 2f0c4ab591
21 changed files with 1406 additions and 169 deletions
@@ -0,0 +1,123 @@
import {
getPenControlPattern,
PEN_CONTROL_PATTERN_IDS,
} from '../data/penControlPatterns';
import type { PenControlCellStyle } from '../shared/penControlDrawUtils';
export const PEN_CONTROL_MODE = 'pen-control-mix' as const;
export type PenControlMode = typeof PEN_CONTROL_MODE;
export const ROWS_PER_PATTERN = 2;
export const MIN_PATTERNS = 0;
export const MAX_PATTERNS = 5;
export const DEFAULT_PATTERN_COUNT = 5;
export interface PenControlRowSpec {
role: 'pair-first' | 'pair-second';
cells: PenControlCellStyle[];
}
export interface PenControlPatternBlock {
patternId: string;
patternName: string;
rows: [PenControlRowSpec, PenControlRowSpec];
}
export interface PenControlSheetLayout {
maxRow: number;
maxCol: number;
cellSize: number;
startX: number;
startY: number;
colGap: number;
rowGap: number;
}
export interface PenControlSheetData {
mode: PenControlMode;
patternCount: number;
blocks: PenControlPatternBlock[];
layout: PenControlSheetLayout;
}
export function clampPatternCount(
requested: number,
maxRow: number,
): number {
const capByRows = Math.floor(maxRow / ROWS_PER_PATTERN);
const capped = Math.min(
MAX_PATTERNS,
Math.max(MIN_PATTERNS, requested),
Math.max(MIN_PATTERNS, capByRows),
);
return capped;
}
function buildRowCells(
maxCol: number,
_role: 'pair-first' | 'pair-second',
): PenControlCellStyle[] {
const cells: PenControlCellStyle[] = [];
for (let c = 0; c < maxCol; c++) {
cells.push(c === 0 ? 'reference' : 'guide');
}
return cells;
}
function normalizePatternIds(
patternIds: string[],
patternCount: number,
): string[] {
const valid = patternIds.filter((id) =>
PEN_CONTROL_PATTERN_IDS.includes(id),
);
const unique: string[] = [];
for (const id of valid) {
if (!unique.includes(id)) unique.push(id);
}
if (unique.length >= patternCount) {
return unique.slice(0, patternCount);
}
const rest = PEN_CONTROL_PATTERN_IDS.filter((id) => !unique.includes(id));
const shuffled = [...rest].sort(() => Math.random() - 0.5);
while (unique.length < patternCount && shuffled.length > 0) {
unique.push(shuffled.shift()!);
}
return unique.slice(0, patternCount);
}
export function generatePenControlMix(options: {
patternIds: string[];
patternCount: number;
layout: PenControlSheetLayout;
}): PenControlSheetData {
const { layout } = options;
const patternCount = clampPatternCount(
options.patternCount,
layout.maxRow,
);
const ids = normalizePatternIds(options.patternIds, patternCount);
const blocks: PenControlPatternBlock[] = ids.map((patternId) => {
const pattern = getPenControlPattern(patternId);
const pairFirst = buildRowCells(layout.maxCol, 'pair-first');
const pairSecond = buildRowCells(layout.maxCol, 'pair-second');
return {
patternId,
patternName: pattern?.name ?? patternId,
rows: [
{ role: 'pair-first', cells: pairFirst },
{ role: 'pair-second', cells: pairSecond },
],
};
});
return {
mode: PEN_CONTROL_MODE,
patternCount: blocks.length,
blocks,
layout,
};
}