Files
doodle-mini/miniprogram/englishPages/letterTracing/draw/dailyCheckinDraw.ts
T
2026-05-20 15:33:06 +08:00

252 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { BaseDrawService } from '../../../core/draw/baseDraw';
import type {
DailyCheckinRow,
DailyCheckinSection,
LetterTracingData,
} from '../generators/letter-tracing-generator';
import {
PRINT_CLEARLY_DASHED,
PRINT_CLEARLY_REGULAR,
fontFamilyOf,
} from '../../../core/font/fontProfiles';
import {
calcLetterFontSizes,
drawClosedFourLineGrid,
drawLetterInFourLineGrid,
loadLetterFont,
} from '../../shared/draw/drawTools';
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';
type DailyCheckinData = Extract<
LetterTracingData,
{ mode: 'letter-tracing-daily-checkin' }
>;
// 页面左右边距(px
const PAGE_MARGIN_X = 14;
// 页面上下边距(px
const PAGE_MARGIN_Y = 14;
// 每个分区内容区左右内边距(px)
const SECTION_PADDING_X = 18;
// 单个格子区列数
const GRID_COLS = 5;
// 单个格子区行数
const GRID_ROWS = 7;
// 格子列间距(px
const GRID_COL_GAP = 6;
// 格子行间距(px
const GRID_ROW_GAP = 8;
import { GRID_COLORS, TRACING_COLORS } from '../../../core/data/tracingStyles';
const GRID_BORDER = GRID_COLORS.border;
const GRID_MIDDLE = GRID_COLORS.middleLine;
const COLOR_BLACK = TRACING_COLORS.strong;
const COLOR_LIGHT_RED = TRACING_COLORS.guide;
export default class DailyCheckinDraw extends BaseDrawService {
async draw(data: DailyCheckinData) {
this.prepareDraw();
await Promise.all([
loadLetterFont(PRINT_CLEARLY_REGULAR),
loadLetterFont(PRINT_CLEARLY_DASHED),
]);
this.drawPageDividers();
const sections = this.getSectionRects();
for (let i = 0; i < sections.length; i++) {
this.drawSection(sections[i], data.sections[i] ?? null);
}
}
private drawPageDividers() {
const verticalX = this.canvasWidth / 2;
const horizontalY = this.canvasHeight / 2;
this.drawLine(
verticalX,
PAGE_MARGIN_Y,
verticalX,
this.canvasHeight - PAGE_MARGIN_Y,
{
isDashed: true,
dashPattern: [5, 5],
color: '#D6D2CC',
lineWidth: 1,
},
);
this.drawLine(
PAGE_MARGIN_X,
horizontalY,
this.canvasWidth - PAGE_MARGIN_X,
horizontalY,
{
isDashed: true,
dashPattern: [5, 5],
color: '#D6D2CC',
lineWidth: 1,
},
);
}
private getSectionRects() {
const halfW = this.canvasWidth / 2;
const halfH = this.canvasHeight / 2;
return [
{
x: PAGE_MARGIN_X,
y: PAGE_MARGIN_Y,
w: halfW - PAGE_MARGIN_X,
h: halfH - PAGE_MARGIN_Y,
},
{
x: halfW,
y: PAGE_MARGIN_Y,
w: this.canvasWidth - PAGE_MARGIN_X - halfW,
h: halfH - PAGE_MARGIN_Y,
},
{
x: PAGE_MARGIN_X,
y: halfH,
w: halfW - PAGE_MARGIN_X,
h: this.canvasHeight - PAGE_MARGIN_Y - halfH,
},
{
x: halfW,
y: halfH,
w: this.canvasWidth - PAGE_MARGIN_X - halfW,
h: this.canvasHeight - PAGE_MARGIN_Y - halfH,
},
];
}
private drawSection(
rect: { x: number; y: number; w: number; h: number },
section: DailyCheckinSection | null,
) {
if (!section) return;
const phrase = getRandomEncouragementPhrase();
drawSectionHeader(this.ctx, rect, '字母每日打卡');
drawSectionFooter(this.ctx, rect, phrase, `- ${section.pageNumber} -`);
this.drawSectionGrid(rect, section.rows);
}
private drawSectionGrid(
rect: { x: number; y: number; w: number; h: number },
rows: DailyCheckinRow[],
) {
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 cellW =
(contentWidth - GRID_COL_GAP * (GRID_COLS - 1)) / GRID_COLS;
const cellH =
(contentHeight - GRID_ROW_GAP * (GRID_ROWS - 1)) / GRID_ROWS;
const pairGap = 16;
const regularSizes = calcLetterFontSizes(
cellH * 0.98,
PRINT_CLEARLY_REGULAR,
);
const dashedSizes = calcLetterFontSizes(
cellH * 0.98,
PRINT_CLEARLY_DASHED,
);
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 < GRID_COLS; colIdx++) {
const x =
rect.x +
SECTION_PADDING_X +
colIdx * (cellW + GRID_COL_GAP);
drawClosedFourLineGrid(this.ctx, x, y, cellW, cellH, {
ink: GRID_BORDER,
middleInk: GRID_MIDDLE,
lineWidth: 1,
dash: [3, 3],
});
const cell = row.cells[colIdx];
if (!cell) continue;
const sizes =
row.style === 'dashed' ? dashedSizes : regularSizes;
const fontFamily = fontFamilyOf(
row.style === 'dashed'
? PRINT_CLEARLY_DASHED
: PRINT_CLEARLY_REGULAR,
);
const centerX = x + cellW / 2;
this.ctx.save();
this.ctx.globalAlpha = cell.alpha;
drawLetterInFourLineGrid(
this.ctx,
cell.upper,
centerX - pairGap / 2,
y,
cellH,
sizes,
{
fontFamily,
color:
cell.color === COLOR_BLACK
? COLOR_BLACK
: COLOR_LIGHT_RED,
},
);
drawLetterInFourLineGrid(
this.ctx,
cell.lower,
centerX + pairGap / 2,
y,
cellH,
sizes,
{
fontFamily,
color:
cell.color === COLOR_BLACK
? COLOR_BLACK
: COLOR_LIGHT_RED,
},
);
this.ctx.restore();
}
}
}
}