202 lines
5.0 KiB
TypeScript
202 lines
5.0 KiB
TypeScript
/**
|
||
* 四线三格 & 字母绘制工具集
|
||
*/
|
||
|
||
import {
|
||
DEFAULT_LETTER_PROFILE,
|
||
fontFamilyOf,
|
||
getLetterMetrics,
|
||
type FontProfile,
|
||
} from '../../../core/font/fontProfiles';
|
||
import { GRID_COLORS, GRID_DASH } from '../../../core/data/tracingStyles';
|
||
import { loadFontFace } from '../../../core/font/fontLoader';
|
||
|
||
// ── 四线三格统一高度(所有页面共用) ──
|
||
export const FOUR_LINE_GRID_H = 43;
|
||
|
||
// ── 字体加载(委托到 core/font/fontLoader) ──
|
||
|
||
/**
|
||
* 加载字体到 Canvas native 渲染管线。
|
||
* 同一 URL 只加载一次;不同 FontProfile 可安全并发调用。
|
||
*/
|
||
export function loadLetterFont(
|
||
profile: FontProfile = DEFAULT_LETTER_PROFILE,
|
||
): Promise<void> {
|
||
return loadFontFace(profile);
|
||
}
|
||
|
||
// ── LetterFontSizes:持有 FontProfile 引用,运行时按字母查表 ──
|
||
|
||
export type LetterFontSizes = {
|
||
_profile: FontProfile;
|
||
_gridH: number;
|
||
};
|
||
|
||
export function calcLetterFontSizes(
|
||
gridH: number,
|
||
profile: FontProfile = DEFAULT_LETTER_PROFILE,
|
||
): LetterFontSizes {
|
||
return { _profile: profile, _gridH: gridH };
|
||
}
|
||
|
||
/** 根据字母和字体配置,计算对应 fontSize */
|
||
export function getLetterFontSize(
|
||
letter: string,
|
||
sizes: LetterFontSizes,
|
||
): number {
|
||
const metrics = getLetterMetrics(letter, sizes._profile);
|
||
return sizes._gridH * metrics.scale;
|
||
}
|
||
|
||
/** 根据字母和字体配置,计算基线 Y 偏移修正(px) */
|
||
function getLetterBaselineOffset(
|
||
letter: string,
|
||
sizes: LetterFontSizes,
|
||
): number {
|
||
const metrics = getLetterMetrics(letter, sizes._profile);
|
||
return sizes._gridH * metrics.baselineOffset;
|
||
}
|
||
|
||
// ── 向后兼容:导出 DESCENDERS 集合 ──
|
||
export { DESCENDERS } from '../../../core/font/fontProfiles';
|
||
|
||
/**
|
||
* 在四线三格中绘制单个字母(基线固定在 y + 2h/3 + baselineOffset)。
|
||
*
|
||
* @param cx 字母水平中心
|
||
* @param gridY 四线三格顶线 Y
|
||
* @param gridH 四线三格总高度
|
||
* @param sizes 预计算的字号(由 calcLetterFontSizes 生成)
|
||
*/
|
||
export function drawLetterInFourLineGrid(
|
||
ctx: RenderingContext,
|
||
letter: string,
|
||
cx: number,
|
||
gridY: number,
|
||
gridH: number,
|
||
sizes: LetterFontSizes,
|
||
options?: {
|
||
fontFamily?: string;
|
||
color?: string;
|
||
bold?: boolean;
|
||
},
|
||
) {
|
||
const fontFamily = options?.fontFamily ?? fontFamilyOf(sizes._profile);
|
||
const color = options?.color ?? '#1a1a1a';
|
||
const bold = options?.bold ?? false;
|
||
|
||
const baselineY =
|
||
gridY + (gridH * 2) / 3 + getLetterBaselineOffset(letter, sizes);
|
||
const fontSize = getLetterFontSize(letter, sizes);
|
||
|
||
ctx.save();
|
||
ctx.font = `${bold ? 'bold ' : ''}${fontSize}px ${fontFamily}`;
|
||
ctx.fillStyle = color;
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'alphabetic';
|
||
ctx.fillText(letter, cx, baselineY);
|
||
ctx.restore();
|
||
}
|
||
|
||
export type FourLineGridStyle = {
|
||
/** 顶线/底线颜色 */
|
||
ink?: string;
|
||
/** 中间两条虚线颜色 */
|
||
middleInk?: string;
|
||
/** 线宽 */
|
||
lineWidth?: number;
|
||
/** 虚线样式 */
|
||
dash?: number[];
|
||
};
|
||
|
||
/**
|
||
* 四线三格(通用)
|
||
* - 顶/底:实线
|
||
* - 中间两条:虚线
|
||
*/
|
||
export function drawFourLineGrid(
|
||
ctx: RenderingContext,
|
||
x: number,
|
||
y: number,
|
||
w: number,
|
||
h: number,
|
||
style?: FourLineGridStyle,
|
||
) {
|
||
const ink = style?.ink ?? GRID_COLORS.border;
|
||
const middleInk = style?.middleInk ?? GRID_COLORS.middleLine;
|
||
const lineWidth = style?.lineWidth ?? 1;
|
||
const dash = style?.dash ?? [...GRID_DASH];
|
||
|
||
const yTop = y;
|
||
const y1 = y + h / 3;
|
||
const y2 = y + (h * 2) / 3;
|
||
const yBot = y + h;
|
||
|
||
ctx.strokeStyle = ink;
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, yTop);
|
||
ctx.lineTo(x + w, yTop);
|
||
ctx.stroke();
|
||
|
||
ctx.strokeStyle = middleInk;
|
||
ctx.setLineDash(dash);
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, y1);
|
||
ctx.lineTo(x + w, y1);
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, y2);
|
||
ctx.lineTo(x + w, y2);
|
||
ctx.stroke();
|
||
|
||
ctx.strokeStyle = ink;
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, yBot);
|
||
ctx.lineTo(x + w, yBot);
|
||
ctx.stroke();
|
||
}
|
||
|
||
/**
|
||
* 闭合四线三格
|
||
* - 外框:闭合矩形
|
||
* - 中间两条:虚线
|
||
*/
|
||
export function drawClosedFourLineGrid(
|
||
ctx: RenderingContext,
|
||
x: number,
|
||
y: number,
|
||
w: number,
|
||
h: number,
|
||
style?: FourLineGridStyle,
|
||
) {
|
||
const ink = style?.ink ?? GRID_COLORS.border;
|
||
const middleInk = style?.middleInk ?? GRID_COLORS.middleLine;
|
||
const lineWidth = style?.lineWidth ?? 1;
|
||
const dash = style?.dash ?? [...GRID_DASH];
|
||
|
||
const y1 = y + h / 3;
|
||
const y2 = y + (h * 2) / 3;
|
||
|
||
ctx.save();
|
||
|
||
ctx.strokeStyle = ink;
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.setLineDash([]);
|
||
ctx.strokeRect(x, y, w, h);
|
||
|
||
ctx.strokeStyle = middleInk;
|
||
ctx.setLineDash(dash);
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, y1);
|
||
ctx.lineTo(x + w, y1);
|
||
ctx.moveTo(x, y2);
|
||
ctx.lineTo(x + w, y2);
|
||
ctx.stroke();
|
||
|
||
ctx.restore();
|
||
}
|