feat: 修改字母描红逻辑,添加字母手写字体

This commit is contained in:
R524809
2026-04-15 17:29:59 +08:00
parent ec5bc44741
commit 4299c4e082
62 changed files with 4135 additions and 894 deletions
@@ -0,0 +1,124 @@
import { BaseDrawService } from '../../../core/draw/baseDraw';
import type {
LetterTracingData,
TracingRow,
} from '../generators/letter-tracing-generator';
import { drawFourLineGrid } from './drawTools';
const LETTER_FACE =
'Helvetica, Arial, "Segoe UI", Roboto, sans-serif';
type CasePairingData = Extract<LetterTracingData, { mode: 'case-pairing' }>;
export default class CasePairingDraw extends BaseDrawService {
async draw(data: CasePairingData) {
this.prepareDraw();
await this.drawHeaderAndDivider();
this.drawContent(data.leftVisualRows, data.rightVisualRows);
this.drawPrintFooter();
}
private drawContent(
leftVisualRows: TracingRow[][],
rightVisualRows: TracingRow[][],
) {
const M = 24;
const y0 = this.currentY + 6;
const bottom = this.canvasHeight - M;
const gapCol = 18;
const colW = (this.canvasWidth - M * 2 - gapCol) / 2;
const maxVisual = Math.max(
leftVisualRows.length,
rightVisualRows.length,
);
const rowSlotH = Math.max(40, (bottom - y0) / maxVisual - 6);
const drawSide = (
blocks: TracingRow[][],
startX: number,
width: number,
) => {
let y = y0;
for (const visualRow of blocks) {
const nSeg = visualRow.length;
const segGap = 6;
const segW = (width - (nSeg - 1) * segGap) / nSeg;
for (let s = 0; s < nSeg; s++) {
this.drawTracingRow(
visualRow[s],
startX + s * (segW + segGap),
y,
rowSlotH,
segW,
);
}
y += rowSlotH + 8;
}
};
drawSide(leftVisualRows, M, colW);
drawSide(rightVisualRows, M + colW + gapCol, colW);
}
private drawTracingRow(
row: TracingRow,
startX: number,
y: number,
rowH: number,
totalWidth: number,
) {
const gap = 4;
const n = row.cells.length;
const cellW = n > 0 ? (totalWidth - gap * (n - 1)) / n : totalWidth;
for (let i = 0; i < n; i++) {
const cx = startX + i * (cellW + gap);
drawFourLineGrid(this.ctx, cx, y, cellW, rowH);
const cell = row.cells[i];
this.drawCharInCell(
cell.char,
cx,
y,
cellW,
rowH,
cell.opacity,
cell.isGuide,
cell.pairChar,
);
}
}
private drawCharInCell(
ch: string,
x: number,
y: number,
w: number,
h: number,
opacity: number,
bold: boolean,
pairChar?: string,
) {
const ctx = this.ctx;
if (opacity <= 0) return;
ctx.save();
ctx.globalAlpha = opacity;
ctx.fillStyle = bold ? '#1a1a1a' : '#4a4a4a';
ctx.textAlign = 'center';
ctx.textBaseline = 'alphabetic';
const baselineY = y + h * 0.72;
if (pairChar) {
const fs = Math.min(h * 0.5, w * 0.22);
ctx.font = `${bold ? 'bold ' : ''}${fs}px ${LETTER_FACE}`;
const gap = fs * 0.2;
const totalW = fs * 2 + gap;
const x0 = x + w / 2 - totalW / 2;
ctx.fillText(ch, x0 + fs * 0.5, baselineY);
ctx.fillText(pairChar, x0 + fs * 1.5 + gap, baselineY);
} else {
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.restore();
}
}