import { BaseDrawService } from '../../../core/draw/baseDraw'; import type { LetterTracingData, TracingRow, } from '../generators/letter-tracing-generator'; import { fontFamilyOf } from '../../shared/data/fontProfiles'; import { calcLetterFontSizes, drawFourLineGrid, drawLetterInFourLineGrid, FOUR_LINE_GRID_H, type LetterFontSizes, } from '../../shared/draw/drawTools'; const COLOR_BLACK = '#1a1a1a'; const COLOR_LIGHT_RED = '#FF8E8E'; const COLUMN_GUTTER = 18; type TwoColumnData = Extract< LetterTracingData, { mode: 'letter-tracing-two-column' } >; /** * 两列练习绘制: * 左栏 A–M(Aa Bb … Mm)、右栏 N–Z(Nn Oo … Zz),每栏 13 行。 * 每行一个完整四线三格,格内绘制大小写配对字母。 * 第一行黑色示范,后续行浅红色渐淡。两栏间距 18px。 */ export default class TwoColumnDraw extends BaseDrawService { private letterSizes: LetterFontSizes = calcLetterFontSizes(FOUR_LINE_GRID_H); async draw(data: TwoColumnData) { this.prepareDraw(); await this.drawHeaderAndDivider(); this.drawContent(data.leftRows, data.rightRows); this.drawPrintFooter(); } private drawContent(leftRows: TracingRow[], rightRows: TracingRow[]) { const M = 24; const lineW = this.canvasWidth - M * 2; const colW = (lineW - COLUMN_GUTTER) / 2; const maxR = Math.max(leftRows.length, rightRows.length); const footerReserve = 56; const availableH = this.canvasHeight - this.currentY - footerReserve - 12; const rowGap = Math.max( 4, (availableH - maxR * FOUR_LINE_GRID_H) / (maxR + 1), ); const y0 = this.currentY + rowGap; const leftX = M; const rightX = M + colW + COLUMN_GUTTER; for (let i = 0; i < maxR; i++) { const y = y0 + i * (FOUR_LINE_GRID_H + rowGap); if (i < leftRows.length) { drawFourLineGrid(this.ctx, leftX, y, colW, FOUR_LINE_GRID_H); this.drawPairRow(leftRows[i], leftX, y, colW); } if (i < rightRows.length) { drawFourLineGrid(this.ctx, rightX, y, colW, FOUR_LINE_GRID_H); this.drawPairRow(rightRows[i], rightX, y, colW); } } } /** * 在一个四线三格内绘制大小写配对(如 A a)。 * rowIndex=0 黑色示范,>0 浅红色。 * cells 里每个 cell 的 char 是大写、pairChar 是小写, * 按 repetitions 排列(第一个实色 → 后续渐淡)。 */ private drawPairRow(row: TracingRow, x: number, y: number, colW: number) { const fontFamily = fontFamilyOf(this.letterSizes._profile); const cells = row.cells; const n = cells.length; if (n === 0) return; const pairCount = n; const slotW = colW / pairCount; const pairGap = 25; for (let i = 0; i < pairCount; i++) { const cell = cells[i]; const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED; const alpha = i >= pairCount - 2 ? 0.5 : 1; const pairCx = x + i * slotW + slotW / 2; this.ctx.save(); this.ctx.globalAlpha = alpha; drawLetterInFourLineGrid( this.ctx, cell.char, pairCx - pairGap / 2, y, FOUR_LINE_GRID_H, this.letterSizes, { fontFamily, color }, ); if (cell.pairChar) { drawLetterInFourLineGrid( this.ctx, cell.pairChar, pairCx + pairGap / 2, y, FOUR_LINE_GRID_H, this.letterSizes, { fontFamily, color }, ); } this.ctx.restore(); } } }