/** * 四线三格 & 字母绘制工具集 */ import { DEFAULT_LETTER_PROFILE, fontFamilyOf, getLetterMetrics, type FontProfile, } from '../data/fontProfiles'; // ── 四线三格统一高度(所有页面共用) ── export const FOUR_LINE_GRID_H = 43; // ── 字体加载(支持多字体,按 URL 去重) ── const _loadedUrls = new Set(); /** * 加载字体到 Canvas native 渲染管线。 * 同一 URL 只加载一次;不同 FontProfile 可安全并发调用。 */ export function loadLetterFont( profile: FontProfile = DEFAULT_LETTER_PROFILE, ): Promise { 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(); } /** * 闭合四线三格 * - 外框:闭合矩形 * - 中间两条:虚线 */ export function drawClosedFourLineGrid( ctx: RenderingContext, x: number, y: number, w: number, h: number, style?: FourLineGridStyle, ) { const ink = style?.ink ?? '#9AA9A2'; const middleInk = style?.middleInk ?? 'rgba(154, 169, 162, 0.65)'; const lineWidth = style?.lineWidth ?? 1; const dash = style?.dash ?? [3, 3]; 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(); }