import { BaseDrawService } from '../../../core/draw/baseDraw'; import type { LetterTracingData, TracingRow, } from '../generators/letter-tracing-generator'; import { drawFourLineGrid } from './drawTools'; const LETTER_FACE = 'Helvetica, Arial, "Segoe UI", Roboto, sans-serif'; type CasePairingData = Extract; export default class CasePairingDraw extends BaseDrawService { async draw(data: CasePairingData) { this.prepareDraw(); await this.drawHeaderAndDivider(); this.drawContent(data.leftVisualRows, data.rightVisualRows); this.drawPrintFooter(); } private drawContent( leftVisualRows: TracingRow[][], rightVisualRows: TracingRow[][], ) { const M = 24; const y0 = this.currentY + 6; const bottom = this.canvasHeight - M; const gapCol = 18; const colW = (this.canvasWidth - M * 2 - gapCol) / 2; const maxVisual = Math.max( leftVisualRows.length, rightVisualRows.length, ); const rowSlotH = Math.max(40, (bottom - y0) / maxVisual - 6); const drawSide = ( blocks: TracingRow[][], startX: number, width: number, ) => { let y = y0; for (const visualRow of blocks) { const nSeg = visualRow.length; const segGap = 6; const segW = (width - (nSeg - 1) * segGap) / nSeg; for (let s = 0; s < nSeg; s++) { this.drawTracingRow( visualRow[s], startX + s * (segW + segGap), y, rowSlotH, segW, ); } y += rowSlotH + 8; } }; drawSide(leftVisualRows, M, colW); drawSide(rightVisualRows, M + colW + gapCol, colW); } private drawTracingRow( row: TracingRow, startX: number, y: number, rowH: number, totalWidth: number, ) { const gap = 4; const n = row.cells.length; const cellW = n > 0 ? (totalWidth - gap * (n - 1)) / n : totalWidth; for (let i = 0; i < n; i++) { const cx = startX + i * (cellW + gap); drawFourLineGrid(this.ctx, cx, y, cellW, rowH); const cell = row.cells[i]; this.drawCharInCell( cell.char, cx, y, cellW, rowH, cell.opacity, cell.isGuide, cell.pairChar, ); } } private drawCharInCell( ch: string, x: number, y: number, w: number, h: number, opacity: number, bold: boolean, pairChar?: string, ) { const ctx = this.ctx; if (opacity <= 0) return; ctx.save(); ctx.globalAlpha = opacity; ctx.fillStyle = bold ? '#1a1a1a' : '#4a4a4a'; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; const baselineY = y + h * 0.72; if (pairChar) { const fs = Math.min(h * 0.5, w * 0.22); ctx.font = `${bold ? 'bold ' : ''}${fs}px ${LETTER_FACE}`; const gap = fs * 0.2; const totalW = fs * 2 + gap; const x0 = x + w / 2 - totalW / 2; ctx.fillText(ch, x0 + fs * 0.5, baselineY); ctx.fillText(pairChar, x0 + fs * 1.5 + gap, baselineY); } else { const fontSize = Math.min(h * 0.72, w * 0.85); ctx.font = `${bold ? 'bold ' : ''}${fontSize}px ${LETTER_FACE}`; ctx.fillText(ch, x + w / 2, baselineY); } ctx.restore(); } }