/** * 图形符号配对绘制服务 */ import { BaseDrawService } from '../../../service/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; } /** * 图形符号配对绘制服务 */ class ShapeSymbolDraw extends BaseDrawService { gridData: ShapeSymbolData | null = null; constructor( canvas: Canvas, ctx: RenderingContext, options?: Record, ) { 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;