170 lines
5.2 KiB
TypeScript
170 lines
5.2 KiB
TypeScript
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);
|
|
}
|
|
}
|