Files
2026-05-20 15:33:06 +08:00

201 lines
6.4 KiB
TypeScript

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();
}
}
}
}
}