feat: 完成四种字母描红开发
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
import type { LetterTracingData } from '../generators/letter-tracing-generator';
|
||||
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 UpperLowerData = Extract<
|
||||
LetterTracingData,
|
||||
{ mode: 'letter-tracing-upper-lower' }
|
||||
>;
|
||||
|
||||
/** 页面左右边距(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 UpperLowerDraw extends BaseDrawService {
|
||||
private letterSizes: LetterFontSizes = calcLetterFontSizes(
|
||||
FOUR_LINE_GRID_H,
|
||||
DASHED_PROFILE,
|
||||
);
|
||||
|
||||
async draw(data: UpperLowerData) {
|
||||
this.prepareDraw();
|
||||
await Promise.allSettled([
|
||||
loadLetterFont(DASHED_PROFILE),
|
||||
loadLetterFont(TITLE_PROFILE),
|
||||
]);
|
||||
await this.drawHeaderAndDivider();
|
||||
this.drawUpperLowerBody(data.upperRows, data.lowerRows);
|
||||
await this.drawPrintFooter();
|
||||
}
|
||||
|
||||
private drawUpperLowerBody(
|
||||
upperRows: string[][],
|
||||
lowerRows: string[][],
|
||||
): void {
|
||||
const lineW = this.canvasWidth - M * 2;
|
||||
const slotW = lineW / 7;
|
||||
|
||||
let y = this.currentY + SECTION_GAP - 7;
|
||||
|
||||
y = this.drawLetterBlock(
|
||||
'Uppercase Letters',
|
||||
upperRows,
|
||||
y,
|
||||
lineW,
|
||||
slotW,
|
||||
);
|
||||
y += SECTION_GAP + 7;
|
||||
this.drawLetterBlock('lowercase letters', lowerRows, y, lineW, slotW);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一个分区:居中标题 + 4 行四线三格与字母。
|
||||
* @returns 该分区最后一行四线三格底边 Y
|
||||
*/
|
||||
private drawLetterBlock(
|
||||
title: string,
|
||||
rows: string[][],
|
||||
startY: number,
|
||||
lineW: number,
|
||||
slotW: number,
|
||||
): number {
|
||||
const ctx = this.ctx;
|
||||
const centerX = M + lineW / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.font = `bold ${TITLE_SIZE}px ${TITLE_FACE}`;
|
||||
ctx.fillStyle = TITLE_COLOR;
|
||||
ctx.textAlign = 'center';
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user