import { BaseDrawService } from '../../../core/draw/baseDraw'; import type { ClockConnectData, ClockConnectProblem, } from '../generators/clock-connect-generator'; import { drawAnalogClock } from '../../clockReading/draw/drawAnalogClock'; import { NUMBER_COLORS } from '../../../constants/colors'; const ROWS = 6; const MARGIN_X = 28; const MARGIN_Y_TOP = 12; const CLOCK_RADIUS = 38 * 1.2; // 放大 1.2 倍 const TIME_BOX_W = 70 * 3 * 0.8 * 0.75; // 放大3倍后缩80%,再缩短长度 const TIME_BOX_H = 28 * 3 * 0.8; // 放大3倍后缩80% const TIME_BOX_RADIUS = 8 * 3 * 0.8; // 放大3倍后缩80% const TIME_FONT_SIZE = 42 * 0.8; // 字体放大3倍后缩80% const CONTENT_BOTTOM_PADDING = 28; const DASH_PATTERN = [7, 5]; const BOX_COLOR = '#555555'; const LINE_POINT_RADIUS = 8; const LINE_POINT_SPACING = 12; const LINE_POINT_COLOR = '#93D333'; /** 从 NUMBER_COLORS 中随机取一个颜色 */ function getRandomColor(): string { const keys = Object.keys(NUMBER_COLORS).map(Number); const key = keys[Math.floor(Math.random() * keys.length)]; return NUMBER_COLORS[key]; } /** * 时钟连线绘制服务: * 左侧 6 个钟表(右侧带连线圆点),右侧 6 个打乱顺序的时间(左侧带连线圆点) * 用户需要将左侧的钟表和右侧对应的时间连线 */ export default class ClockConnectDraw extends BaseDrawService { async draw(data: ClockConnectData) { if (!data?.clocks?.length) return; this.prepareDraw(); await this.drawHeaderAndDivider(); this.drawConnectGrid(data.clocks, data.shuffledTimes); this.drawPrintFooter({ mode: 'A' }); } private drawConnectGrid( clocks: ClockConnectProblem[], shuffledTimes: ClockConnectProblem[], ) { const { ctx, canvasWidth, canvasHeight } = this; const contentTop = this.currentY; const availableH = canvasHeight - contentTop - CONTENT_BOTTOM_PADDING; const clockBlockH = CLOCK_RADIUS * 2; const totalGridH = ROWS * clockBlockH; const rowGap = Math.max( 8, (availableH - totalGridH - MARGIN_Y_TOP * 2) / (ROWS - 1), ); const startY = contentTop + MARGIN_Y_TOP; const rowH = clockBlockH + rowGap; // 向中间靠拢 const leftCenterX = MARGIN_X + CLOCK_RADIUS + 40; const rightCenterX = canvasWidth - MARGIN_X - TIME_BOX_W / 2 - 40; for (let i = 0; i < ROWS && i < clocks.length; i++) { const clock = clocks[i]; const cellTopY = startY + i * rowH; const clockCy = cellTopY + CLOCK_RADIUS; // 绘制左侧钟表 drawAnalogClock(ctx, { cx: leftCenterX, cy: clockCy, radius: CLOCK_RADIUS, hour: clock.hour, minute: clock.minute, }); // 绘制钟表右侧的连线圆点 const leftDotX = leftCenterX + CLOCK_RADIUS + LINE_POINT_SPACING + LINE_POINT_RADIUS; ctx.fillStyle = LINE_POINT_COLOR; ctx.beginPath(); ctx.arc(leftDotX, clockCy, LINE_POINT_RADIUS, 0, Math.PI * 2); ctx.fill(); // 绘制右侧时间框 const timeItem = shuffledTimes[i]; const boxX = rightCenterX - TIME_BOX_W / 2; const boxY = clockCy - TIME_BOX_H / 2; const color = getRandomColor(); this.drawTimeBox(boxX, boxY, timeItem.timeText, color); // 绘制时间框左侧的连线圆点 const rightDotX = boxX - LINE_POINT_SPACING - LINE_POINT_RADIUS; ctx.fillStyle = LINE_POINT_COLOR; ctx.beginPath(); ctx.arc(rightDotX, clockCy, LINE_POINT_RADIUS, 0, Math.PI * 2); ctx.fill(); } } /** 绘制圆角虚线矩形 + 居中彩色时间文本 */ private drawTimeBox(x: number, y: number, text: string, color: string) { const { ctx } = this; ctx.save(); ctx.strokeStyle = BOX_COLOR; ctx.lineWidth = 1.8; ctx.setLineDash(DASH_PATTERN); this.strokeRoundRect(x, y, TIME_BOX_W, TIME_BOX_H, TIME_BOX_RADIUS); ctx.setLineDash([]); ctx.fillStyle = color; ctx.font = `bold ${TIME_FONT_SIZE}px "Microsoft Yahei", sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, x + TIME_BOX_W / 2, y + TIME_BOX_H / 2); ctx.restore(); } private strokeRoundRect( x: number, y: number, w: number, h: number, r: number, ) { const { ctx } = this; const radius = Math.min(r, w / 2, h / 2); ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + w - radius, y); ctx.arcTo(x + w, y, x + w, y + radius, radius); ctx.lineTo(x + w, y + h - radius); ctx.arcTo(x + w, y + h, x + w - radius, y + h, radius); ctx.lineTo(x + radius, y + h); ctx.arcTo(x, y + h, x, y + h - radius, radius); ctx.lineTo(x, y + radius); ctx.arcTo(x, y, x + radius, y, radius); ctx.closePath(); ctx.stroke(); } }