/** * 图形涂色服务 * 根据图形类型绘制到Canvas * * 支持的图形类型: * - circle:圆形 * - ellipse:椭圆 * - square:正方形 * - rectangle:矩形 * - triangle:三角形 */ import { BaseDrawService } from '../core/draw/baseDraw'; import { ShapeCard } from '../constants/shapes'; import { drawShape } from './drawShape'; class ShapeDrawService extends BaseDrawService { shapes: ShapeCard[]; constructor( canvas: Canvas, ctx: RenderingContext, options?: Record, ) { options = options || {}; super(canvas, ctx, { title: '找一找 涂 色', subTitle: '给图形涂上相同的颜色', ...options, }); this.shapes = []; } async draw(shapes: ShapeCard[]) { this.setPrintConfig(); this.shapes = shapes.slice(0, 6); // 最多6个图形 this.clear(); this.setPaper(); await this.drawHeader(); this.drawDivider(); this.drawLegend(); this.drawContent(); } drawLegend() { const { ctx, shapes } = this; if (shapes.length <= 0) return; const legendTopY = this.currentY; const shapeSize = 67; // 200/3≈67 const rectWidth = 60; // 180/3=60 const rectHeight = 27; // 80/3≈27 const startY = legendTopY + 42; // 125/3≈42 const len = shapes.length; // 计算示例图形的间距 const totalWidth = len * shapeSize + (len - 1) * 13; // 40/3≈13 const startX = (this.canvasWidth - totalWidth) / 2 + shapeSize / 2; // 绘制所有图形 shapes.forEach((shape: ShapeCard, index: number) => { const x = startX + index * (shapeSize + 13); // 40/3≈13 const y = startY; // 绘制示例图形 drawShape(ctx, shape, x, y, shapeSize, shape.fillColor); const rectangleX = x - rectWidth / 2; const rectangleY = y + shapeSize / 2 + 3; // 10/3≈3 // 绘制图形名称 ctx.fillStyle = '#333'; ctx.font = 'bold 12px "Microsoft Yahei"'; // 36/3=12 ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText( shape.name, rectangleX + rectWidth / 2, rectangleY + rectHeight / 2, rectWidth, ); }); this.currentY = startY + shapeSize / 2 + 3 + rectHeight + 28; this.drawDivider(); } drawContent() { const { ctx, shapes } = this; if (shapes.length <= 0) return; // 逻辑像素尺寸(原始尺寸除以3) const ROW_COUNT = 6; const VERTICAL_GAP = 40; // 120/3=40 const shapeSize = 60; // 180/3=60 const leftMargin = 53; // 160/3≈53 const rightMargin = 53; // 160/3≈53 const legendBottomY = this.currentY; const topGap = 20; // 60/3=20 const contentTop = legendBottomY + topGap; // 可用宽度 const contentWidth = this.canvasWidth - leftMargin - rightMargin; // 每行最多能放多少个图形(考虑最小间距,逻辑像素) const minGap = 13; // 40/3≈13 const maxPerRow = Math.floor( (contentWidth + minGap) / (shapeSize + minGap), ); // 总共要绘制多少个图形(每行都填满) const totalShapes = maxPerRow * ROW_COUNT; // 生成所有要绘制的图形(打乱顺序,保证随机性) let allShapes: ShapeCard[] = []; for (let i = 0; i < Math.ceil(totalShapes / shapes.length); i++) { allShapes = allShapes.concat(shapes); } allShapes = allShapes.slice(0, totalShapes); // 洗牌算法彻底打乱 for (let i = allShapes.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [allShapes[i], allShapes[j]] = [allShapes[j], allShapes[i]]; } // 生成所有图形的位置 let positions: { x: number; y: number }[] = []; for (let row = 0; row < ROW_COUNT; row++) { // 本行起始Y const y = contentTop + row * (shapeSize + VERTICAL_GAP) + shapeSize / 2; // 本行实际间距 const gap = maxPerRow > 1 ? Math.max( minGap, (contentWidth - maxPerRow * shapeSize) / (maxPerRow - 1), ) : 0; // 本行起始X(水平居中) let startX = leftMargin; if (maxPerRow > 1) { const rowWidth = maxPerRow * shapeSize + (maxPerRow - 1) * gap; startX = leftMargin + (contentWidth - rowWidth) / 2; } else { startX = leftMargin + (contentWidth - shapeSize) / 2; } for (let col = 0; col < maxPerRow; col++) { positions.push({ x: startX + col * (shapeSize + gap) + shapeSize / 2, y: y, }); } } // 修正:只保留前 totalShapes 个位置,防止多出一行 positions = positions.slice(0, totalShapes); // 再次彻底打乱位置顺序,保证排列无规律 for (let i = positions.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [positions[i], positions[j]] = [positions[j], positions[i]]; } // 记录实际绘制的图形数量 console.log( `绘制图形:计划${totalShapes}个,实际${positions.length}个;每行${maxPerRow}个,共${ROW_COUNT}行;边距 左右=${leftMargin}/${rightMargin},顶部=${topGap}(基于legend底线)`, ); // 绘制所有图形 positions.forEach((pos, index) => { const shape = allShapes[index]; // 随机旋转角度,范围为±90度(即最大180度) const rotation = (Math.random() - 0.5) * Math.PI; // ±90度 = ±π/2 弧度,Math.PI为180度 ctx.save(); ctx.translate(pos.x, pos.y); ctx.rotate(rotation); drawShape(ctx, shape, 0, 0, shapeSize, 'transparent'); ctx.restore(); }); } // drawLine 已由基类的 drawDivider 替代,保留此方法以保持兼容 drawLine(linY: number) { this.drawDivider(); } } export default ShapeDrawService;