Files
doodle-mini/miniprogram/englishPages/shared/draw/drawTools.ts
T
2026-05-19 18:24:43 +08:00

202 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 四线三格 & 字母绘制工具集
*/
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();
}