feat: 完成三类字母描红开发

This commit is contained in:
R524809
2026-04-17 15:59:55 +08:00
parent 6c98e96506
commit 15ad5cce75
13 changed files with 565 additions and 233 deletions
@@ -1,87 +1,133 @@
import { BaseDrawService } from '../../../core/draw/baseDraw';
import type { LetterTracingData } from '../generators/letter-tracing-generator';
import { drawFourLineGrid } from './drawTools';
const LETTER_FACE =
'Helvetica, Arial, "Segoe UI", Roboto, sans-serif';
import {
PRINT_CLEARLY_BOLD,
PRINT_CLEARLY_DASHED,
fontFamilyOf,
} from '../data/fontProfiles';
import {
calcLetterFontSizes,
drawFourLineGrid,
drawLetterInFourLineGrid,
FOUR_LINE_GRID_H,
loadLetterFont,
type LetterFontSizes,
} from './drawTools';
type OverviewData = Extract<LetterTracingData, { mode: 'overview' }>;
/** 页面左右边距(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 OverviewDraw extends BaseDrawService {
private letterSizes: LetterFontSizes = calcLetterFontSizes(
FOUR_LINE_GRID_H,
DASHED_PROFILE,
);
async draw(data: OverviewData) {
this.prepareDraw();
await Promise.allSettled([
loadLetterFont(DASHED_PROFILE),
loadLetterFont(TITLE_PROFILE),
]);
await this.drawHeaderAndDivider();
this.drawContent(data.upperRows, data.lowerRows);
this.drawPrintFooter();
this.drawOverviewBody(data.upperRows, data.lowerRows);
await this.drawPrintFooter();
}
private drawContent(upperRows: string[][], lowerRows: string[][]) {
const ctx = this.ctx;
const M = 24;
let y = this.currentY + 6;
const innerW = this.canvasWidth - M * 2;
private drawOverviewBody(
upperRows: string[][],
lowerRows: string[][],
): void {
const lineW = this.canvasWidth - M * 2;
const slotW = lineW / 7;
const drawTitle = (t: string) => {
ctx.fillStyle = '#111';
ctx.font = 'bold 17px sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText(t, M, y);
y += 24;
};
let y = this.currentY + SECTION_GAP - 7;
const drawBand = (rows: string[][]) => {
for (const line of rows) {
const n = line.length;
const gap = 4;
const cellW = (innerW - (n - 1) * gap) / n;
const cellH = 30;
for (let i = 0; i < n; i++) {
const cx = M + i * (cellW + gap);
drawFourLineGrid(ctx, cx, y, cellW, cellH);
this.drawCharInCell(
line[i],
cx,
y,
cellW,
cellH,
0.9,
true,
);
}
y += cellH + 8;
}
};
drawTitle('Uppercase Letters');
drawBand(upperRows);
y += 6;
this.drawDashedDivider(y, M, '#bbb');
y += 12;
drawTitle('lowercase Letters');
drawBand(lowerRows);
y = this.drawLetterBlock(
'Uppercase Letters',
upperRows,
y,
lineW,
slotW,
);
y += SECTION_GAP + 7;
this.drawLetterBlock('lowercase letters', lowerRows, y, lineW, slotW);
}
private drawCharInCell(
ch: string,
x: number,
y: number,
w: number,
h: number,
opacity: number,
bold: boolean,
) {
/**
* 绘制一个分区:居中标题 + 4 行四线三格与字母。
* @returns 该分区最后一行四线三格底边 Y
*/
private drawLetterBlock(
title: string,
rows: string[][],
startY: number,
lineW: number,
slotW: number,
): number {
const ctx = this.ctx;
if (opacity <= 0) return;
const centerX = M + lineW / 2;
ctx.save();
ctx.globalAlpha = opacity;
ctx.fillStyle = bold ? '#1a1a1a' : '#4a4a4a';
ctx.font = `bold ${TITLE_SIZE}px ${TITLE_FACE}`;
ctx.fillStyle = TITLE_COLOR;
ctx.textAlign = 'center';
ctx.textBaseline = 'alphabetic';
const baselineY = y + h * 0.72;
const fontSize = Math.min(h * 0.72, w * 0.85);
ctx.font = `${bold ? 'bold ' : ''}${fontSize}px ${LETTER_FACE}`;
ctx.fillText(ch, x + w / 2, baselineY);
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;
}
}