Files
doodle-mini/miniprogram/englishPages/shared/draw/drawTools.ts
T
2026-04-17 15:59:55 +08:00

178 lines
4.5 KiB
TypeScript

/**
* 四线三格 & 字母绘制工具集
*/
import {
DEFAULT_LETTER_PROFILE,
fontFamilyOf,
getLetterMetrics,
type FontProfile,
} from '../data/fontProfiles';
// ── 四线三格统一高度(所有页面共用) ──
export const FOUR_LINE_GRID_H = 43;
// ── 字体加载(支持多字体,按 URL 去重) ──
const _loadedUrls = new Set<string>();
/**
* 加载字体到 Canvas native 渲染管线。
* 同一 URL 只加载一次;不同 FontProfile 可安全并发调用。
*/
export function loadLetterFont(
profile: FontProfile = DEFAULT_LETTER_PROFILE,
): Promise<void> {
if (_loadedUrls.has(profile.url)) return Promise.resolve();
return new Promise((resolve, reject) => {
wx.loadFontFace({
family: profile.name,
source: `url("${profile.url}")`,
scopes: ['native'],
success: () => {
_loadedUrls.add(profile.url);
resolve();
},
fail: (err) => {
console.error(`loadLetterFont [${profile.name}] failed`, err);
reject(err);
},
});
});
}
// ── 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 '../data/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 ?? '#322E25';
const middleInk = style?.middleInk ?? 'rgba(50, 46, 37, 0.3)';
const lineWidth = style?.lineWidth ?? 1;
const dash = style?.dash ?? [4, 4];
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();
}