import { PinyinBaseDraw } from '../../shared/pinyinBaseDraw'; import { generatePinyinDaily, type PinyinDailySection, } from '../../shared/pinyinDailyGenerator'; import { TONEOZ_PINYIN, fontFamilyOf } from '../../../core/font/fontProfiles'; import { loadFontFace } from '../../../core/font/fontLoader'; import { SECTION_PADDING_TOP, SECTION_PADDING_BOTTOM, HEADER_TITLE_GAP, HEADER_META_GAP, CONTENT_TOP_GAP, FOOTER_PHRASE_GAP, FOOTER_INDEX_GAP, drawSectionHeader, drawSectionFooter, getRandomEncouragementPhrase, } from '../../../core/draw/dailyPracticeDrawHelper'; const PAGE_MARGIN = 14; const SECTION_PADDING_X = 18; const GRID_COL_GAP = 6; const GRID_ROW_GAP = 8; export default class PinyinDailyDraw extends PinyinBaseDraw { async draw(dayIndex: number) { const dailyData = generatePinyinDaily(dayIndex); this.prepareDraw(); await loadFontFace(TONEOZ_PINYIN); this.drawPageDividers(); const sectionRects = this.getSectionRects(); for (let i = 0; i < sectionRects.length; i++) { const section = dailyData.sections[i]; if (section) { this.drawSection(sectionRects[i], section, i + 1); } } } private drawPageDividers() { const cx = this.canvasWidth / 2; const cy = this.canvasHeight / 2; const dashStyle = { isDashed: true, dashPattern: [5, 5], color: '#D6D2CC', lineWidth: 1, }; this.drawLine( cx, PAGE_MARGIN, cx, this.canvasHeight - PAGE_MARGIN, dashStyle, ); this.drawLine( PAGE_MARGIN, cy, this.canvasWidth - PAGE_MARGIN, cy, dashStyle, ); } private getSectionRects() { const halfW = this.canvasWidth / 2; const halfH = this.canvasHeight / 2; return [ { x: PAGE_MARGIN, y: PAGE_MARGIN, w: halfW - PAGE_MARGIN, h: halfH - PAGE_MARGIN, }, { x: halfW, y: PAGE_MARGIN, w: this.canvasWidth - PAGE_MARGIN - halfW, h: halfH - PAGE_MARGIN, }, { x: PAGE_MARGIN, y: halfH, w: halfW - PAGE_MARGIN, h: this.canvasHeight - PAGE_MARGIN - halfH, }, { x: halfW, y: halfH, w: this.canvasWidth - PAGE_MARGIN - halfW, h: this.canvasHeight - PAGE_MARGIN - halfH, }, ]; } private drawSection( rect: { x: number; y: number; w: number; h: number }, section: PinyinDailySection, sectionIndex: number, ) { const phrase = getRandomEncouragementPhrase(); drawSectionHeader(this.ctx, rect, '拼音每日打卡'); this.drawSectionGrid(rect, section); drawSectionFooter(this.ctx, rect, phrase, `- ${sectionIndex} -`); } private drawSectionGrid( rect: { x: number; y: number; w: number; h: number }, section: PinyinDailySection, ) { const contentTop = rect.y + SECTION_PADDING_TOP + HEADER_TITLE_GAP + HEADER_META_GAP + CONTENT_TOP_GAP; const contentBottom = rect.y + rect.h - SECTION_PADDING_BOTTOM - FOOTER_PHRASE_GAP - FOOTER_INDEX_GAP; const contentHeight = contentBottom - contentTop; const contentWidth = rect.w - SECTION_PADDING_X * 2; const cols = section.cols; const rows = section.rows; const cellW = (contentWidth - GRID_COL_GAP * (cols - 1)) / cols; const cellH = (contentHeight - GRID_ROW_GAP * (rows.length - 1)) / rows.length; const fontFamily = fontFamilyOf(TONEOZ_PINYIN); for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) { const row = rows[rowIdx]; const y = contentTop + rowIdx * (cellH + GRID_ROW_GAP); for (let colIdx = 0; colIdx < cols; colIdx++) { const cell = row.cells[colIdx]; const x = rect.x + SECTION_PADDING_X + colIdx * (cellW + GRID_COL_GAP); this.drawFourLineGrid(x, y, cellW, cellH); if (cell) { const cx = x + cellW / 2; const fontSize = Math.round(cellH * 0.7); const baselineY = y + (cellH * 2) / 3; let charToDraw = cell.char; const variants = section.task.variants || []; const hasVariants = variants.length > 0; if (cols === 5) { if (hasVariants) { if (colIdx === 0) { charToDraw = section.task.char; } else { charToDraw = variants[colIdx - 1] || section.task.char; } } else { charToDraw = section.task.char; } } else if (cols === 4) { let drawnRowIdx = -1; if (rowIdx === 0) drawnRowIdx = 0; else if (rowIdx === 1) drawnRowIdx = 1; else if (rowIdx === 3) drawnRowIdx = 2; else if (rowIdx === 5) drawnRowIdx = 3; if (drawnRowIdx !== -1 && hasVariants) { charToDraw = variants[drawnRowIdx] || section.task.char; } else { charToDraw = section.task.char; } } this.ctx.save(); this.ctx.font = `${fontSize}px ${fontFamily}`; this.ctx.fillStyle = cell.color; this.ctx.globalAlpha = cell.alpha; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'alphabetic'; this.ctx.fillText(charToDraw, cx, baselineY); this.ctx.restore(); } } } } }