feat: 添加控笔页
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
import { drawTianZiGrid } from '../../shared/drawUtils';
|
||||
import { getPenControlPattern } from '../data/penControlPatterns';
|
||||
import type { PenControlSheetData } from '../generators/penControlGenerator';
|
||||
import {
|
||||
drawPenControlInCell,
|
||||
type PenControlCellStyle,
|
||||
} from '../shared/penControlDrawUtils';
|
||||
|
||||
/** 控笔练习固定 8 列 × 10 行 */
|
||||
const LAYOUT = {
|
||||
topGap: 17,
|
||||
leftMargin: 40,
|
||||
rightMargin: 40,
|
||||
bottomMargin: 40,
|
||||
targetCols: 8,
|
||||
targetRows: 10,
|
||||
minColGap: 6,
|
||||
rowGap: 8,
|
||||
} as const;
|
||||
|
||||
export default class PenControlDrawService extends BaseDrawService {
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, unknown>,
|
||||
) {
|
||||
super(canvas, ctx, {
|
||||
title: '控笔组合练习',
|
||||
subTitle: '每种图形占两行田字格',
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
getMaxGridLayout(): { maxRow: number; maxCol: number } {
|
||||
return { maxRow: LAYOUT.targetRows, maxCol: LAYOUT.targetCols };
|
||||
}
|
||||
|
||||
computeSheetLayout(maxRow: number, maxCol: number) {
|
||||
const content = this.getContentRect();
|
||||
const { minColGap, rowGap } = LAYOUT;
|
||||
|
||||
const cellByWidth =
|
||||
(content.width - (maxCol - 1) * minColGap) / maxCol;
|
||||
const cellByHeight =
|
||||
(content.height - (maxRow - 1) * rowGap) / maxRow;
|
||||
const cellSize = Math.max(
|
||||
16,
|
||||
Math.floor(Math.min(cellByWidth, cellByHeight)),
|
||||
);
|
||||
|
||||
const actualColGap =
|
||||
maxCol > 1
|
||||
? (content.width - maxCol * cellSize) / (maxCol - 1)
|
||||
: 0;
|
||||
const actualRowGap =
|
||||
maxRow > 1
|
||||
? Math.min(
|
||||
rowGap,
|
||||
(content.height - maxRow * cellSize) / (maxRow - 1),
|
||||
)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
maxRow,
|
||||
maxCol,
|
||||
cellSize,
|
||||
startX: content.left,
|
||||
startY: content.top,
|
||||
colGap: actualColGap,
|
||||
rowGap: actualRowGap,
|
||||
};
|
||||
}
|
||||
|
||||
async draw(data: PenControlSheetData) {
|
||||
this.prepareDraw();
|
||||
await this.drawHeaderAndDivider();
|
||||
const layout = this.computeSheetLayout(
|
||||
data.layout.maxRow,
|
||||
data.layout.maxCol,
|
||||
);
|
||||
this.drawSheet({ ...data, layout });
|
||||
this.drawPrintFooter();
|
||||
}
|
||||
|
||||
private getContentRect() {
|
||||
const topGap = LAYOUT.topGap;
|
||||
const leftMargin = LAYOUT.leftMargin;
|
||||
const rightMargin = LAYOUT.rightMargin;
|
||||
const bottomMargin = LAYOUT.bottomMargin;
|
||||
const contentTop = this.currentY + topGap;
|
||||
const contentWidth = this.canvasWidth - leftMargin - rightMargin;
|
||||
const contentHeight =
|
||||
this.canvasHeight - contentTop - bottomMargin;
|
||||
|
||||
return {
|
||||
top: contentTop,
|
||||
left: leftMargin,
|
||||
width: contentWidth,
|
||||
height: contentHeight,
|
||||
};
|
||||
}
|
||||
|
||||
private drawSheet(data: PenControlSheetData) {
|
||||
const { ctx } = this;
|
||||
const { layout, blocks } = data;
|
||||
const {
|
||||
startX,
|
||||
startY,
|
||||
cellSize,
|
||||
colGap,
|
||||
rowGap,
|
||||
maxCol,
|
||||
maxRow,
|
||||
} = layout;
|
||||
|
||||
for (let row = 0; row < maxRow; row++) {
|
||||
for (let col = 0; col < maxCol; col++) {
|
||||
const cx = startX + col * (cellSize + colGap) + cellSize / 2;
|
||||
const cy = startY + row * (cellSize + rowGap) + cellSize / 2;
|
||||
drawTianZiGrid({ ctx, cx, cy, size: cellSize });
|
||||
}
|
||||
}
|
||||
|
||||
let globalRow = 0;
|
||||
for (const block of blocks) {
|
||||
for (const rowSpec of block.rows) {
|
||||
if (globalRow >= maxRow) break;
|
||||
|
||||
const pattern = getPenControlPattern(block.patternId);
|
||||
if (pattern) {
|
||||
for (let col = 0; col < maxCol; col++) {
|
||||
const cx =
|
||||
startX + col * (cellSize + colGap) + cellSize / 2;
|
||||
const cy =
|
||||
startY +
|
||||
globalRow * (cellSize + rowGap) +
|
||||
cellSize / 2;
|
||||
const style: PenControlCellStyle =
|
||||
rowSpec.cells[col] ?? 'guide';
|
||||
const dashed =
|
||||
rowSpec.role === 'pair-second' && col >= 1;
|
||||
drawPenControlInCell(
|
||||
ctx,
|
||||
pattern,
|
||||
cx,
|
||||
cy,
|
||||
cellSize,
|
||||
style,
|
||||
{ dashed },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
globalRow++;
|
||||
}
|
||||
|
||||
if (globalRow >= maxRow) break;
|
||||
}
|
||||
}
|
||||
|
||||
/** 供页面在绘制前计算 layout */
|
||||
buildLayoutForGenerate(): ReturnType<
|
||||
PenControlDrawService['computeSheetLayout']
|
||||
> {
|
||||
const { maxRow, maxCol } = this.getMaxGridLayout();
|
||||
return this.computeSheetLayout(maxRow, maxCol);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user