feat: 分栏对照开发,字体缩放、定位调整
This commit is contained in:
@@ -4,20 +4,30 @@ import type {
|
||||
LetterTracingData,
|
||||
HighlightGrid,
|
||||
} from '../generators/letter-tracing-generator';
|
||||
import { drawFourLineGrid } from './drawTools';
|
||||
import {
|
||||
calcLetterFontSizes,
|
||||
drawFourLineGrid,
|
||||
drawLetterInFourLineGrid,
|
||||
FOUR_LINE_GRID_H,
|
||||
getLetterFontSize,
|
||||
TOY_FONT_LETTER,
|
||||
type LetterFontSizes,
|
||||
} from './drawTools';
|
||||
|
||||
const LINE_INK_ALPHA = 'rgba(50, 46, 37, 1)';
|
||||
const ACCENT_RED = 'rgba(252, 46, 0, 1)';
|
||||
//'Helvetica, Arial, "Segoe UI", Roboto, sans-serif';
|
||||
const LETTER_FACE = 'Roboto, sans-serif';
|
||||
const TITLE_EMOJI_FONT =
|
||||
"16px 'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif";
|
||||
const LETTER_FACE = `${TOY_FONT_LETTER}, Roboto, sans-serif`;
|
||||
// const TITLE_EMOJI_FONT =
|
||||
// "16px 'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif";
|
||||
const TITLE_TEXT_FONT = '14px sans-serif';
|
||||
const TITLE_ICON_GAP = 4;
|
||||
|
||||
type PictureData = Extract<LetterTracingData, { mode: 'picture-tracing' }>;
|
||||
|
||||
export default class PictureTracingDraw extends BaseDrawService {
|
||||
private letterSizes: LetterFontSizes = calcLetterFontSizes(FOUR_LINE_GRID_H);
|
||||
|
||||
async draw(data: PictureData) {
|
||||
this.prepareDraw();
|
||||
await this.drawHeaderAndDivider();
|
||||
@@ -141,7 +151,6 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
// 每行:left:24 w:547 h:43,top 分别 524.5 / 590.5 / 656.5 / 722.5
|
||||
const lineX = 24;
|
||||
const lineW = 547;
|
||||
const lineH = 43;
|
||||
const lineTops = [524.5, 590.5, 656.5, 722.5];
|
||||
|
||||
// 8 → 6 → 3 → 1 组(每组为 Upper+Lower),左对齐,形成倒置直角三角形
|
||||
@@ -150,19 +159,17 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
|
||||
for (let r = 0; r < lineTops.length; r++) {
|
||||
const y = lineTops[r];
|
||||
drawFourLineGrid(ctx, lineX, y, lineW, lineH);
|
||||
drawFourLineGrid(ctx, lineX, y, lineW, FOUR_LINE_GRID_H);
|
||||
|
||||
const n = groupsPerLine[r] ?? 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
// 第一组不透明,后面逐次更透明
|
||||
const alpha = n <= 1 ? 1 : 1 - (i / (n - 1)) * 0.7; // 1 → 0.3
|
||||
const alpha = n <= 1 ? 1 : 1 - (i / (n - 1)) * 0.7;
|
||||
this.drawUpperLowerPairInFourLines(
|
||||
teaching.upper,
|
||||
teaching.lower,
|
||||
lineX + i * slotW,
|
||||
y,
|
||||
slotW,
|
||||
lineH,
|
||||
alpha,
|
||||
);
|
||||
}
|
||||
@@ -170,17 +177,7 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在四线三格中绘制 Upper+Lower(遵循书写基线规范)
|
||||
*
|
||||
* 四线三格的 4 条线(从上到下):
|
||||
* Top = y ── 顶线
|
||||
* Midline = y + h/3 ── 中线(虚线)
|
||||
* Baseline = y + 2h/3 ── 基线(虚线)
|
||||
* Bottom = y + h ── 底线
|
||||
*
|
||||
* 大写字母:高度占满 Top → Baseline(即第一格 + 第二格),字号 ≈ h * 2/3
|
||||
* 小写字母:高度占满 Midline → Baseline(即第二格),字号 ≈ h * 1/3
|
||||
* 两个字母共享 Baseline,间距远小于组间距
|
||||
* 在四线三格中绘制 Upper+Lower 对(共享基线,间距远小于组间距)
|
||||
*/
|
||||
private drawUpperLowerPairInFourLines(
|
||||
upper: string,
|
||||
@@ -188,40 +185,45 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
alpha: number,
|
||||
) {
|
||||
const ctx = this.ctx;
|
||||
const sizes = this.letterSizes;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, alpha));
|
||||
ctx.fillStyle = 'rgba(26, 26, 26, 1)';
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
|
||||
const baselineY = y + (h * 2) / 3;
|
||||
|
||||
// 大写占满 Top→Baseline(2 格高),小写占满 Midline→Baseline(1 格高)
|
||||
const upperFs = h * (2 / 3);
|
||||
const lowerFs = h * (1 / 3);
|
||||
|
||||
// 字母间距:固定 3px,远小于组间距(slotW ≈ 68)
|
||||
const pairGap = 3;
|
||||
|
||||
const upperFs = getLetterFontSize(upper, sizes);
|
||||
ctx.font = `${upperFs}px ${LETTER_FACE}`;
|
||||
const upperW = ctx.measureText(upper).width;
|
||||
|
||||
const lowerFs = getLetterFontSize(lower, sizes);
|
||||
ctx.font = `${lowerFs}px ${LETTER_FACE}`;
|
||||
const lowerW = ctx.measureText(lower).width;
|
||||
|
||||
const totalPairW = upperW + pairGap + lowerW;
|
||||
const pairStartX = x + (w - totalPairW) / 2;
|
||||
const color = 'rgba(26, 26, 26, 1)';
|
||||
|
||||
ctx.font = `${upperFs}px ${LETTER_FACE}`;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(upper, pairStartX, baselineY);
|
||||
|
||||
ctx.font = `${lowerFs}px ${LETTER_FACE}`;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(lower, pairStartX + upperW + pairGap, baselineY);
|
||||
drawLetterInFourLineGrid(
|
||||
ctx,
|
||||
upper,
|
||||
pairStartX + upperW / 2,
|
||||
y,
|
||||
FOUR_LINE_GRID_H,
|
||||
sizes,
|
||||
{ fontFamily: LETTER_FACE, color },
|
||||
);
|
||||
drawLetterInFourLineGrid(
|
||||
ctx,
|
||||
lower,
|
||||
pairStartX + upperW + pairGap + lowerW / 2,
|
||||
y,
|
||||
FOUR_LINE_GRID_H,
|
||||
sizes,
|
||||
{ fontFamily: LETTER_FACE, color },
|
||||
);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -257,7 +259,8 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
const hlIcon = '\u{1F50D}';
|
||||
ctx.font = TITLE_EMOJI_FONT;
|
||||
ctx.font =
|
||||
"16px 'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif";
|
||||
ctx.fillText(hlIcon, titleX, titleY + 1);
|
||||
const hlIconW = ctx.measureText(hlIcon).width;
|
||||
|
||||
@@ -312,7 +315,8 @@ export default class PictureTracingDraw extends BaseDrawService {
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
const traceIcon = '\u{1F58A}\u{FE0F}';
|
||||
ctx.font = TITLE_EMOJI_FONT;
|
||||
ctx.font =
|
||||
"16px 'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif";
|
||||
ctx.fillText(traceIcon, titleX, titleY);
|
||||
const traceIconW = ctx.measureText(traceIcon).width;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user