266 lines
8.5 KiB
TypeScript
266 lines
8.5 KiB
TypeScript
/**
|
||
* 图形符号配对绘制服务
|
||
*/
|
||
|
||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||
import { drawShapeSymbolShape } from '../shapes/shapeSymbolShapes';
|
||
|
||
/**
|
||
* 图形符号配对数据
|
||
*/
|
||
export interface ShapeSymbolData {
|
||
/** 图例映射(4个图形和符号的对应关系) */
|
||
legendMapping: Array<{
|
||
shapeId: string;
|
||
symbol: string;
|
||
color: string;
|
||
}>;
|
||
/** 练习区域的行数据(6行,每两行一组) */
|
||
practiceRows: Array<
|
||
Array<{ shapeId: string | null; symbol: string | null }>
|
||
>;
|
||
/** 图形颜色映射 */
|
||
shapeColorMap: Record<string, string>;
|
||
}
|
||
|
||
/**
|
||
* 图形符号配对绘制服务
|
||
*/
|
||
class ShapeSymbolDraw extends BaseDrawService {
|
||
gridData: ShapeSymbolData | null = null;
|
||
|
||
constructor(
|
||
canvas: Canvas,
|
||
ctx: RenderingContext,
|
||
options?: Record<string, any>,
|
||
) {
|
||
super(canvas, ctx, options);
|
||
}
|
||
|
||
/**
|
||
* 绘制图形符号配对内容
|
||
*/
|
||
async draw(gridData: ShapeSymbolData) {
|
||
if (!gridData || !gridData.legendMapping || !gridData.practiceRows) {
|
||
return;
|
||
}
|
||
|
||
this.gridData = gridData;
|
||
this.prepareDraw();
|
||
await this.drawHeaderAndDivider();
|
||
this.drawContent({
|
||
ctx: this.ctx,
|
||
gridData: this.gridData,
|
||
canvasWidth: this.canvasWidth,
|
||
startY: this.currentY,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 绘制内容区域
|
||
*/
|
||
private drawContent(params: {
|
||
ctx: RenderingContext;
|
||
gridData: ShapeSymbolData;
|
||
canvasWidth: number;
|
||
startY: number;
|
||
}): void {
|
||
const { ctx, gridData, canvasWidth } = params;
|
||
let { startY } = params;
|
||
|
||
startY = startY + 25;
|
||
|
||
const margin = 40; // 左右边距
|
||
const legendCols = 4; // 图例列数
|
||
const legendRows = 2; // 图例行数
|
||
const cellSize = 65; // 每个格子的宽度
|
||
const gridWidth = cellSize * legendCols;
|
||
|
||
// 绘制参考网格(上面的九宫格)
|
||
const legendStartX =
|
||
margin + (canvasWidth - margin * 2 - gridWidth) / 2; // 居中
|
||
const legendStartY = startY;
|
||
|
||
// 绘制参考网格的网格线
|
||
this.drawGridLines(
|
||
legendStartX,
|
||
legendStartY,
|
||
cellSize,
|
||
cellSize,
|
||
legendCols,
|
||
legendRows,
|
||
);
|
||
|
||
// 绘制图例内容
|
||
// 优化常量设置,减少重复计算,增强可维护性
|
||
// const legendColSpacing = 24; // 列间距(px),如需调整只此一处
|
||
// const legendCellHeight = cellSize; // 图例格子的高度,当前与cellSize一致
|
||
|
||
gridData.legendMapping.forEach((mapping, index: number) => {
|
||
if (!mapping) return;
|
||
|
||
const shapeX = legendStartX + index * cellSize + cellSize / 2;
|
||
const shapeY = legendStartY + cellSize / 2;
|
||
|
||
const shapeSize = 42;
|
||
const shapeColor = mapping.color || '#000';
|
||
drawShapeSymbolShape(
|
||
ctx,
|
||
mapping.shapeId,
|
||
shapeX,
|
||
shapeY,
|
||
shapeSize,
|
||
shapeColor,
|
||
);
|
||
|
||
// 第二行:绘制符号(使用 emoji)
|
||
const symbolX = legendStartX + index * cellSize + cellSize / 2;
|
||
const symbolY = legendStartY + cellSize + cellSize / 2;
|
||
this.drawSymbol(ctx, symbolX, symbolY, mapping.symbol, 30);
|
||
});
|
||
|
||
const dividerY = legendStartY + legendRows * cellSize + 30;
|
||
this.drawDashedDivider(dividerY, margin);
|
||
|
||
// 绘制练习区域(六行七列,使用网格线,确保正方形格子)
|
||
const practiceStartY = dividerY + 30;
|
||
const practiceCols = 8;
|
||
const practiceRows = 8;
|
||
const practiceCellSize = 60; // 练习格子宽度
|
||
const practiceGridWidth = practiceCellSize * practiceCols;
|
||
|
||
// 计算练习区域起始X位置(居中)
|
||
const practiceStartX =
|
||
margin + (canvasWidth - margin * 2 - practiceGridWidth) / 2;
|
||
|
||
// 绘制练习区域网格线(使用 drawGridLines)
|
||
this.drawGridLines(
|
||
practiceStartX,
|
||
practiceStartY,
|
||
practiceCellSize,
|
||
practiceCellSize,
|
||
practiceCols,
|
||
practiceRows,
|
||
);
|
||
|
||
// 绘制练习内容
|
||
for (let row = 0; row < practiceRows; row++) {
|
||
const practiceRow = gridData.practiceRows[row];
|
||
if (!practiceRow) continue;
|
||
|
||
const rowY = practiceStartY + row * practiceCellSize;
|
||
const cellCenterY = rowY + practiceCellSize / 2;
|
||
|
||
for (let col = 0; col < practiceCols; col++) {
|
||
const cell = practiceRow[col];
|
||
if (!cell) continue;
|
||
|
||
const colX = practiceStartX + col * practiceCellSize;
|
||
const cellCenterX = colX + practiceCellSize / 2;
|
||
|
||
// 如果第一行(图形行),绘制图形(使用相同颜色,无边框,居中)
|
||
if (row % 2 === 0 && cell.shapeId) {
|
||
const shapeSize = 42;
|
||
const shapeColor =
|
||
gridData.shapeColorMap[cell.shapeId] || '#000';
|
||
drawShapeSymbolShape(
|
||
ctx,
|
||
cell.shapeId,
|
||
cellCenterX,
|
||
cellCenterY,
|
||
shapeSize,
|
||
shapeColor,
|
||
);
|
||
}
|
||
// 第二行(符号行)留空,让小朋友涂符号
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 绘制符号(使用路径绘制,类似 shapeSymbolShapes.ts)
|
||
*/
|
||
private drawSymbol(
|
||
ctx: RenderingContext,
|
||
x: number,
|
||
y: number,
|
||
symbol: string,
|
||
size: number,
|
||
): void {
|
||
ctx.save();
|
||
ctx.translate(x, y);
|
||
|
||
const lineWidth = size * 0.15; // 线条宽度
|
||
const halfSize = size / 2;
|
||
const strokeLength = halfSize * 0.7; // 线条长度
|
||
|
||
ctx.strokeStyle = '#000';
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.lineCap = 'round';
|
||
ctx.lineJoin = 'round';
|
||
|
||
switch (symbol) {
|
||
case '+':
|
||
// 加号:横线和竖线
|
||
ctx.beginPath();
|
||
// 横线
|
||
ctx.moveTo(-strokeLength, 0);
|
||
ctx.lineTo(strokeLength, 0);
|
||
// 竖线
|
||
ctx.moveTo(0, -strokeLength);
|
||
ctx.lineTo(0, strokeLength);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
case '-':
|
||
// 减号:横线
|
||
ctx.beginPath();
|
||
ctx.moveTo(-strokeLength, 0);
|
||
ctx.lineTo(strokeLength, 0);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
case '×':
|
||
// 乘号:两条斜线
|
||
ctx.beginPath();
|
||
// 左上到右下
|
||
ctx.moveTo(-strokeLength * 0.7, -strokeLength * 0.7);
|
||
ctx.lineTo(strokeLength * 0.7, strokeLength * 0.7);
|
||
// 右上到左下
|
||
ctx.moveTo(strokeLength * 0.7, -strokeLength * 0.7);
|
||
ctx.lineTo(-strokeLength * 0.7, strokeLength * 0.7);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
case '✓':
|
||
// 对号:勾,整体更大,右侧的线更长
|
||
const checkScale = 1.2; // 对号整体放大1.2倍
|
||
ctx.beginPath();
|
||
const checkStartX = -strokeLength * 0.5 * checkScale;
|
||
const checkStartY = -strokeLength * 0.2 * checkScale;
|
||
const checkMidX = -strokeLength * 0.1 * checkScale;
|
||
const checkMidY = strokeLength * 0.3 * checkScale;
|
||
const checkEndX = strokeLength * 1.0 * checkScale; // 增加右侧长度
|
||
const checkEndY = -strokeLength * 0.4 * checkScale; // 稍微向上调整
|
||
ctx.moveTo(checkStartX, checkStartY);
|
||
ctx.lineTo(checkMidX, checkMidY);
|
||
ctx.lineTo(checkEndX, checkEndY);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
default:
|
||
// 默认绘制加号
|
||
ctx.beginPath();
|
||
ctx.moveTo(-strokeLength, 0);
|
||
ctx.lineTo(strokeLength, 0);
|
||
ctx.moveTo(0, -strokeLength);
|
||
ctx.lineTo(0, strokeLength);
|
||
ctx.stroke();
|
||
}
|
||
|
||
ctx.restore();
|
||
}
|
||
}
|
||
|
||
export default ShapeSymbolDraw;
|