feat: 完成四种字母描红开发
This commit is contained in:
@@ -3,121 +3,128 @@ import type {
|
||||
LetterTracingData,
|
||||
TracingRow,
|
||||
} from '../generators/letter-tracing-generator';
|
||||
import { drawFourLineGrid } from './drawTools';
|
||||
import { fontFamilyOf } from '../data/fontProfiles';
|
||||
import {
|
||||
calcLetterFontSizes,
|
||||
drawFourLineGrid,
|
||||
drawLetterInFourLineGrid,
|
||||
FOUR_LINE_GRID_H,
|
||||
type LetterFontSizes,
|
||||
} from './drawTools';
|
||||
const COLOR_BLACK = '#1a1a1a';
|
||||
const COLOR_LIGHT_RED = '#FF8E8E';
|
||||
|
||||
const LETTER_FACE = 'Helvetica, Arial, "Segoe UI", Roboto, sans-serif';
|
||||
/** 左右大写/小写两栏之间的间距(相对行宽 + 下限,避免过窄屏过小) */
|
||||
const COLUMN_GUTTER_RATIO = 0.02;
|
||||
const COLUMN_GUTTER_MIN = 14;
|
||||
|
||||
type CasePairingData = Extract<LetterTracingData, { mode: 'case-pairing' }>;
|
||||
/**
|
||||
* 单栏内字母排版区域占栏宽比例(<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.leftVisualRows, data.rightVisualRows);
|
||||
this.drawContent(data.leftRows, data.rightRows);
|
||||
this.drawPrintFooter();
|
||||
}
|
||||
|
||||
private drawContent(
|
||||
leftVisualRows: TracingRow[][],
|
||||
rightVisualRows: TracingRow[][],
|
||||
) {
|
||||
private drawContent(leftRows: TracingRow[], rightRows: 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 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 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;
|
||||
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,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
drawSide(leftVisualRows, M, colW);
|
||||
drawSide(rightVisualRows, M + colW + gapCol, colW);
|
||||
if (i < rightRows.length) {
|
||||
this.drawLettersInHalf(
|
||||
rightRows[i],
|
||||
rightX,
|
||||
y,
|
||||
halfContentW,
|
||||
INTRA_COLUMN_LETTER_BAND_RATIO,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private drawTracingRow(
|
||||
/**
|
||||
* 在四线三格的半侧区域内绘制字母。
|
||||
* 第一个字母黑色,后续字母浅红色。
|
||||
*/
|
||||
private drawLettersInHalf(
|
||||
row: TracingRow,
|
||||
startX: number,
|
||||
x: number,
|
||||
y: number,
|
||||
rowH: number,
|
||||
totalWidth: number,
|
||||
columnW: number,
|
||||
letterBandRatio: number,
|
||||
) {
|
||||
const gap = 4;
|
||||
const n = row.cells.length;
|
||||
const cellW = n > 0 ? (totalWidth - gap * (n - 1)) / n : totalWidth;
|
||||
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 cx = startX + i * (cellW + gap);
|
||||
drawFourLineGrid(this.ctx, cx, y, cellW, rowH);
|
||||
const cell = row.cells[i];
|
||||
this.drawCharInCell(
|
||||
const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED;
|
||||
const cx = x + inset + slotW * i + slotW / 2;
|
||||
|
||||
drawLetterInFourLineGrid(
|
||||
this.ctx,
|
||||
cell.char,
|
||||
cx,
|
||||
y,
|
||||
cellW,
|
||||
rowH,
|
||||
cell.opacity,
|
||||
cell.isGuide,
|
||||
cell.pairChar,
|
||||
FOUR_LINE_GRID_H,
|
||||
this.letterSizes,
|
||||
{ fontFamily: fontFamilyOf(this.letterSizes._profile), color },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
import type {
|
||||
LetterTracingData,
|
||||
TracingRow,
|
||||
} from '../generators/letter-tracing-generator';
|
||||
import { fontFamilyOf } from '../data/fontProfiles';
|
||||
import {
|
||||
calcLetterFontSizes,
|
||||
drawFourLineGrid,
|
||||
drawLetterInFourLineGrid,
|
||||
FOUR_LINE_GRID_H,
|
||||
type LetterFontSizes,
|
||||
} from './drawTools';
|
||||
const COLOR_BLACK = '#1a1a1a';
|
||||
const COLOR_LIGHT_RED = 'rgba(252, 46, 0, 0.3)';
|
||||
|
||||
/** 左右大写/小写两栏之间的间距(相对行宽 + 下限,避免过窄屏过小) */
|
||||
const COLUMN_GUTTER_RATIO = 0.02;
|
||||
const COLUMN_GUTTER_MIN = 14;
|
||||
|
||||
/**
|
||||
* 单栏内字母排版区域占栏宽比例(<1 则同栏字母更紧凑、字间距更小)
|
||||
*/
|
||||
const INTRA_COLUMN_LETTER_BAND_RATIO = 1;
|
||||
|
||||
type ColumnCompareData = Extract<LetterTracingData, { mode: 'column-compare' }>;
|
||||
|
||||
/**
|
||||
* 分栏对照绘制:
|
||||
* 四线三格横跨整页宽度,字母分左右两侧书写——
|
||||
* 左栏大写、右栏小写(各 repetitions 个),中间留间距;同栏内字母略收紧。
|
||||
* 每侧第一个字母黑色,后续浅红色。
|
||||
*/
|
||||
export default class ColumnCompareDraw extends BaseDrawService {
|
||||
private letterSizes: LetterFontSizes =
|
||||
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
||||
|
||||
async draw(data: ColumnCompareData) {
|
||||
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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,7 @@ export function drawLetterInFourLineGrid(
|
||||
bold?: boolean;
|
||||
},
|
||||
) {
|
||||
const fontFamily =
|
||||
options?.fontFamily ?? fontFamilyOf(sizes._profile);
|
||||
const fontFamily = options?.fontFamily ?? fontFamilyOf(sizes._profile);
|
||||
const color = options?.color ?? '#1a1a1a';
|
||||
const bold = options?.bold ?? false;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
import type { LetterTracingData } from '../generators/letter-tracing-generator';
|
||||
import PictureTracingDraw from './pictureTracingDraw';
|
||||
import singleLetterDraw from './singleLetterDraw';
|
||||
import CasePairingDraw from './casePairingDraw';
|
||||
import ColumnCompareDraw from './columnCompareDraw';
|
||||
import OverviewDraw from './overviewDraw';
|
||||
import TwoColumnDraw from './twoColumnDraw';
|
||||
import UpperLowerDraw from './upperLowerDraw';
|
||||
|
||||
/**
|
||||
* 字母描红绘制分发层。
|
||||
@@ -12,10 +12,10 @@ import OverviewDraw from './overviewDraw';
|
||||
export default class LetterTracingDraw extends BaseDrawService {
|
||||
async draw(data: LetterTracingData) {
|
||||
const DrawClass = {
|
||||
'picture-tracing': PictureTracingDraw,
|
||||
'case-pairing': CasePairingDraw,
|
||||
'column-compare': ColumnCompareDraw,
|
||||
overview: OverviewDraw,
|
||||
'letter-tracing-single': singleLetterDraw,
|
||||
'letter-tracing-upper-lower': UpperLowerDraw,
|
||||
'letter-tracing-case-pairing': CasePairingDraw,
|
||||
'letter-tracing-two-column': TwoColumnDraw,
|
||||
}[data.mode];
|
||||
|
||||
const delegate = new DrawClass(this.canvas, this.ctx, this.options);
|
||||
|
||||
+9
-6
@@ -19,23 +19,26 @@ const ACCENT_RED = 'rgba(252, 46, 0, 1)';
|
||||
const TITLE_TEXT_FONT = '14px sans-serif';
|
||||
const TITLE_ICON_GAP = 4;
|
||||
|
||||
type PictureData = Extract<LetterTracingData, { mode: 'picture-tracing' }>;
|
||||
type SingleLetterData = Extract<
|
||||
LetterTracingData,
|
||||
{ mode: 'letter-tracing-single' }
|
||||
>;
|
||||
|
||||
export default class PictureTracingDraw extends BaseDrawService {
|
||||
export default class SingleLetterDraw extends BaseDrawService {
|
||||
private letterSizes: LetterFontSizes =
|
||||
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
||||
private get letterFace(): string {
|
||||
return fontFamilyOf(this.letterSizes._profile);
|
||||
}
|
||||
|
||||
async draw(data: PictureData) {
|
||||
async draw(data: SingleLetterData) {
|
||||
this.prepareDraw();
|
||||
await this.drawHeaderAndDivider();
|
||||
await this.drawContent(data);
|
||||
this.drawPrintFooter();
|
||||
}
|
||||
|
||||
private async drawContent(data: PictureData) {
|
||||
private async drawContent(data: SingleLetterData) {
|
||||
const { teaching, highlightGrid, traceImgPath } = data;
|
||||
const ctx = this.ctx;
|
||||
|
||||
@@ -79,7 +82,7 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
);
|
||||
}
|
||||
|
||||
this.drawSentence(
|
||||
this.drawSingleLetterSentence(
|
||||
teaching.sentence,
|
||||
teaching.upper,
|
||||
TOP_X,
|
||||
@@ -228,7 +231,7 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private drawSentence(
|
||||
private drawSingleLetterSentence(
|
||||
sentence: string,
|
||||
letter: string,
|
||||
x: number,
|
||||
@@ -0,0 +1,124 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
import type {
|
||||
LetterTracingData,
|
||||
TracingRow,
|
||||
} from '../generators/letter-tracing-generator';
|
||||
import { fontFamilyOf } from '../data/fontProfiles';
|
||||
import {
|
||||
calcLetterFontSizes,
|
||||
drawFourLineGrid,
|
||||
drawLetterInFourLineGrid,
|
||||
FOUR_LINE_GRID_H,
|
||||
type LetterFontSizes,
|
||||
} from './drawTools';
|
||||
|
||||
const COLOR_BLACK = '#1a1a1a';
|
||||
const COLOR_LIGHT_RED = '#FF8E8E';
|
||||
const COLUMN_GUTTER = 18;
|
||||
|
||||
type TwoColumnData = Extract<
|
||||
LetterTracingData,
|
||||
{ mode: 'letter-tracing-two-column' }
|
||||
>;
|
||||
|
||||
/**
|
||||
* 两列练习绘制:
|
||||
* 左栏 A–M(Aa Bb … Mm)、右栏 N–Z(Nn Oo … Zz),每栏 13 行。
|
||||
* 每行一个完整四线三格,格内绘制大小写配对字母。
|
||||
* 第一行黑色示范,后续行浅红色渐淡。两栏间距 18px。
|
||||
*/
|
||||
export default class TwoColumnDraw extends BaseDrawService {
|
||||
private letterSizes: LetterFontSizes =
|
||||
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
||||
|
||||
async draw(data: TwoColumnData) {
|
||||
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 colW = (lineW - COLUMN_GUTTER) / 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 leftX = M;
|
||||
const rightX = M + colW + COLUMN_GUTTER;
|
||||
|
||||
for (let i = 0; i < maxR; i++) {
|
||||
const y = y0 + i * (FOUR_LINE_GRID_H + rowGap);
|
||||
|
||||
if (i < leftRows.length) {
|
||||
drawFourLineGrid(this.ctx, leftX, y, colW, FOUR_LINE_GRID_H);
|
||||
this.drawPairRow(leftRows[i], leftX, y, colW);
|
||||
}
|
||||
if (i < rightRows.length) {
|
||||
drawFourLineGrid(this.ctx, rightX, y, colW, FOUR_LINE_GRID_H);
|
||||
this.drawPairRow(rightRows[i], rightX, y, colW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一个四线三格内绘制大小写配对(如 A a)。
|
||||
* rowIndex=0 黑色示范,>0 浅红色。
|
||||
* cells 里每个 cell 的 char 是大写、pairChar 是小写,
|
||||
* 按 repetitions 排列(第一个实色 → 后续渐淡)。
|
||||
*/
|
||||
private drawPairRow(row: TracingRow, x: number, y: number, colW: number) {
|
||||
const fontFamily = fontFamilyOf(this.letterSizes._profile);
|
||||
const cells = row.cells;
|
||||
const n = cells.length;
|
||||
if (n === 0) return;
|
||||
|
||||
const pairCount = n;
|
||||
const slotW = colW / pairCount;
|
||||
|
||||
const pairGap = 25;
|
||||
|
||||
for (let i = 0; i < pairCount; i++) {
|
||||
const cell = cells[i];
|
||||
const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED;
|
||||
const alpha = i >= pairCount - 2 ? 0.5 : 1;
|
||||
const pairCx = x + i * slotW + slotW / 2;
|
||||
|
||||
this.ctx.save();
|
||||
this.ctx.globalAlpha = alpha;
|
||||
|
||||
drawLetterInFourLineGrid(
|
||||
this.ctx,
|
||||
cell.char,
|
||||
pairCx - pairGap / 2,
|
||||
y,
|
||||
FOUR_LINE_GRID_H,
|
||||
this.letterSizes,
|
||||
{ fontFamily, color },
|
||||
);
|
||||
|
||||
if (cell.pairChar) {
|
||||
drawLetterInFourLineGrid(
|
||||
this.ctx,
|
||||
cell.pairChar,
|
||||
pairCx + pairGap / 2,
|
||||
y,
|
||||
FOUR_LINE_GRID_H,
|
||||
this.letterSizes,
|
||||
{ fontFamily, color },
|
||||
);
|
||||
}
|
||||
|
||||
this.ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -14,7 +14,10 @@ import {
|
||||
type LetterFontSizes,
|
||||
} from './drawTools';
|
||||
|
||||
type OverviewData = Extract<LetterTracingData, { mode: 'overview' }>;
|
||||
type UpperLowerData = Extract<
|
||||
LetterTracingData,
|
||||
{ mode: 'letter-tracing-upper-lower' }
|
||||
>;
|
||||
|
||||
/** 页面左右边距(margin,px),用于排版时留白 */
|
||||
const M = 24;
|
||||
@@ -40,24 +43,24 @@ const TITLE_FACE = fontFamilyOf(TITLE_PROFILE);
|
||||
* 字母总览:上区大写、下区小写;每区标题 + 4 行四线三格(7+7+6+6)。
|
||||
* 列宽按 7 列均分;后两行 6 字左对齐,与前两行逐列对齐(占前 6 列)。
|
||||
*/
|
||||
export default class OverviewDraw extends BaseDrawService {
|
||||
export default class UpperLowerDraw extends BaseDrawService {
|
||||
private letterSizes: LetterFontSizes = calcLetterFontSizes(
|
||||
FOUR_LINE_GRID_H,
|
||||
DASHED_PROFILE,
|
||||
);
|
||||
|
||||
async draw(data: OverviewData) {
|
||||
async draw(data: UpperLowerData) {
|
||||
this.prepareDraw();
|
||||
await Promise.allSettled([
|
||||
loadLetterFont(DASHED_PROFILE),
|
||||
loadLetterFont(TITLE_PROFILE),
|
||||
]);
|
||||
await this.drawHeaderAndDivider();
|
||||
this.drawOverviewBody(data.upperRows, data.lowerRows);
|
||||
this.drawUpperLowerBody(data.upperRows, data.lowerRows);
|
||||
await this.drawPrintFooter();
|
||||
}
|
||||
|
||||
private drawOverviewBody(
|
||||
private drawUpperLowerBody(
|
||||
upperRows: string[][],
|
||||
lowerRows: string[][],
|
||||
): void {
|
||||
Reference in New Issue
Block a user