feat: 开发完成拼音每日一练
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user