131 lines
3.9 KiB
TypeScript
131 lines
3.9 KiB
TypeScript
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
|
import type {
|
|
LetterTracingData,
|
|
TracingRow,
|
|
} from '../generators/letter-tracing-generator';
|
|
import { fontFamilyOf } from '../../shared/data/fontProfiles';
|
|
import {
|
|
calcLetterFontSizes,
|
|
drawFourLineGrid,
|
|
drawLetterInFourLineGrid,
|
|
FOUR_LINE_GRID_H,
|
|
type LetterFontSizes,
|
|
} from '../../shared/draw/drawTools';
|
|
const COLOR_BLACK = '#1a1a1a';
|
|
const COLOR_LIGHT_RED = '#FF8E8E';
|
|
|
|
/** 左右大写/小写两栏之间的间距(相对行宽 + 下限,避免过窄屏过小) */
|
|
const COLUMN_GUTTER_RATIO = 0.02;
|
|
const COLUMN_GUTTER_MIN = 14;
|
|
|
|
/**
|
|
* 单栏内字母排版区域占栏宽比例(<1 则同栏字母更紧凑、字间距更小)
|
|
*/
|
|
const INTRA_COLUMN_LETTER_BAND_RATIO = 1;
|
|
|
|
type CasePairingData = Extract<
|
|
LetterTracingData,
|
|
{ mode: 'letter-tracing-case-pairing' }
|
|
>;
|
|
|
|
/**
|
|
* 分栏对照绘制:
|
|
* 四线三格横跨整页宽度,字母分左右两侧书写——
|
|
* 左栏大写、右栏小写(各 repetitions 个),中间留间距;同栏内字母略收紧。
|
|
* 每侧第一个字母黑色,后续浅红色。
|
|
*/
|
|
export default class CasePairingDraw extends BaseDrawService {
|
|
private letterSizes: LetterFontSizes =
|
|
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
|
|
|
async draw(data: CasePairingData) {
|
|
this.prepareDraw();
|
|
await this.drawHeaderAndDivider();
|
|
this.drawContent(data.leftRows, data.rightRows);
|
|
this.drawPrintFooter();
|
|
}
|
|
|
|
private drawContent(leftRows: TracingRow[], rightRows: TracingRow[]) {
|
|
const M = 24;
|
|
const lineW = this.canvasWidth - M * 2;
|
|
const maxR = Math.max(leftRows.length, rightRows.length);
|
|
|
|
const footerReserve = 56;
|
|
const availableH =
|
|
this.canvasHeight - this.currentY - footerReserve - 12;
|
|
const rowGap = Math.max(
|
|
4,
|
|
(availableH - maxR * FOUR_LINE_GRID_H) / (maxR + 1),
|
|
);
|
|
|
|
const y0 = this.currentY + rowGap;
|
|
|
|
const columnGutter = Math.max(
|
|
COLUMN_GUTTER_MIN,
|
|
Math.round(lineW * COLUMN_GUTTER_RATIO),
|
|
);
|
|
const halfContentW = (lineW - columnGutter) / 2;
|
|
const leftX = M;
|
|
const rightX = M + halfContentW + columnGutter;
|
|
|
|
for (let i = 0; i < maxR; i++) {
|
|
const y = y0 + i * (FOUR_LINE_GRID_H + rowGap);
|
|
|
|
drawFourLineGrid(this.ctx, M, y, lineW, FOUR_LINE_GRID_H);
|
|
|
|
if (i < leftRows.length) {
|
|
this.drawLettersInHalf(
|
|
leftRows[i],
|
|
leftX,
|
|
y,
|
|
halfContentW,
|
|
INTRA_COLUMN_LETTER_BAND_RATIO,
|
|
);
|
|
}
|
|
if (i < rightRows.length) {
|
|
this.drawLettersInHalf(
|
|
rightRows[i],
|
|
rightX,
|
|
y,
|
|
halfContentW,
|
|
INTRA_COLUMN_LETTER_BAND_RATIO,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 在四线三格的半侧区域内绘制字母。
|
|
* 第一个字母黑色,后续字母浅红色。
|
|
*/
|
|
private drawLettersInHalf(
|
|
row: TracingRow,
|
|
x: number,
|
|
y: number,
|
|
columnW: number,
|
|
letterBandRatio: number,
|
|
) {
|
|
const n = row.cells.length;
|
|
if (n === 0) return;
|
|
|
|
const bandW = columnW * letterBandRatio;
|
|
const inset = (columnW - bandW) / 2;
|
|
const slotW = bandW / n;
|
|
for (let i = 0; i < n; i++) {
|
|
const cell = row.cells[i];
|
|
const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED;
|
|
const cx = x + inset + slotW * i + slotW / 2;
|
|
|
|
drawLetterInFourLineGrid(
|
|
this.ctx,
|
|
cell.char,
|
|
cx,
|
|
y,
|
|
FOUR_LINE_GRID_H,
|
|
this.letterSizes,
|
|
{ fontFamily: fontFamilyOf(this.letterSizes._profile), color },
|
|
);
|
|
}
|
|
}
|
|
}
|