import { BaseDrawService } from '../../../core/draw/baseDraw'; import type { LetterTracingData } from '../generators/letter-tracing-generator'; import { PRINT_CLEARLY_BOLD, PRINT_CLEARLY_DASHED, fontFamilyOf, } from '../../shared/data/fontProfiles'; import { calcLetterFontSizes, drawFourLineGrid, drawLetterInFourLineGrid, FOUR_LINE_GRID_H, loadLetterFont, type LetterFontSizes, } from '../../shared/draw/drawTools'; type UpperLowerData = Extract< LetterTracingData, { mode: 'letter-tracing-upper-lower' } >; /** 页面左右边距(margin,px),用于排版时留白 */ const M = 24; /** 上下两大区(Uppercase / lowercase)之间的间距 */ const SECTION_GAP = 25; /** 标题字号 */ const TITLE_SIZE = 28; /** 标题与第一行四线三格的间距 */ const TITLE_GAP = 25; /** 同一区域内相邻四线三格行之间的间距 */ const ROW_GAP = 24; const TITLE_COLOR = '#000'; /** 浅红色描红(不透明 hex,避免打印时半透明发灰) */ const TRACE_LIGHT_RED = '#FF8E8E'; const DASHED_PROFILE = PRINT_CLEARLY_DASHED; const TITLE_PROFILE = PRINT_CLEARLY_BOLD; const LETTER_FACE = fontFamilyOf(DASHED_PROFILE); const TITLE_FACE = fontFamilyOf(TITLE_PROFILE); /** * 字母总览:上区大写、下区小写;每区标题 + 4 行四线三格(7+7+6+6)。 * 列宽按 7 列均分;后两行 6 字左对齐,与前两行逐列对齐(占前 6 列)。 */ export default class UpperLowerDraw extends BaseDrawService { private letterSizes: LetterFontSizes = calcLetterFontSizes( FOUR_LINE_GRID_H, DASHED_PROFILE, ); async draw(data: UpperLowerData) { this.prepareDraw(); await Promise.allSettled([ loadLetterFont(DASHED_PROFILE), loadLetterFont(TITLE_PROFILE), ]); await this.drawHeaderAndDivider(); this.drawUpperLowerBody(data.upperRows, data.lowerRows); await this.drawPrintFooter(); } private drawUpperLowerBody( upperRows: string[][], lowerRows: string[][], ): void { const lineW = this.canvasWidth - M * 2; const slotW = lineW / 7; let y = this.currentY + SECTION_GAP - 7; y = this.drawLetterBlock( 'Uppercase Letters', upperRows, y, lineW, slotW, ); y += SECTION_GAP + 7; this.drawLetterBlock('lowercase letters', lowerRows, y, lineW, slotW); } /** * 绘制一个分区:居中标题 + 4 行四线三格与字母。 * @returns 该分区最后一行四线三格底边 Y */ private drawLetterBlock( title: string, rows: string[][], startY: number, lineW: number, slotW: number, ): number { const ctx = this.ctx; const centerX = M + lineW / 2; ctx.save(); ctx.font = `bold ${TITLE_SIZE}px ${TITLE_FACE}`; ctx.fillStyle = TITLE_COLOR; ctx.textAlign = 'center'; ctx.textBaseline = 'top'; ctx.fillText(title, centerX, startY); ctx.restore(); let y = startY + TITLE_SIZE + TITLE_GAP; for (let r = 0; r < rows.length; r++) { const chars = rows[r]; drawFourLineGrid(ctx, M, y, lineW, FOUR_LINE_GRID_H); /** 与 7 字行共用列宽 slotW,后两行 6 字左对齐(占前 6 列),与上行逐列对齐 */ const offsetX = M; for (let i = 0; i < chars.length; i++) { const cx = offsetX + slotW * (i + 0.5); drawLetterInFourLineGrid( ctx, chars[i], cx, y, FOUR_LINE_GRID_H, this.letterSizes, { fontFamily: LETTER_FACE, color: TRACE_LIGHT_RED, }, ); } y += FOUR_LINE_GRID_H; if (r < rows.length - 1) { y += ROW_GAP; } } return y; } }