// 每个分区内容区顶部内边距(px) export const SECTION_PADDING_TOP = 12; // 每个分区内容区底部内边距(px) export const SECTION_PADDING_BOTTOM = 10; // 标题与上一元素的垂直间距(px) export const HEADER_TITLE_GAP = 32; // 标题与元信息(如日期、学号等)的间距(px) export const HEADER_META_GAP = 22; // 正文内容距标题/元信息顶部的间距(px) export const CONTENT_TOP_GAP = 14; // 页脚短语与内容之间的间距(px) export const FOOTER_PHRASE_GAP = 26; // 页脚索引与短语之间的间距(px) export const FOOTER_INDEX_GAP = 22; // 20 条鼓励语词库 export const ENCOURAGEMENT_PHRASES: ReadonlyArray = [ '书山有路勤为径', '日拱一卒 不期而至', '宝剑锋从磨砺出', '梅花香自苦寒来', '业精于勤荒于嬉', '行百里者半九十', '锲而不舍金石可镂', '读万卷书行万里路', '滴水穿石非一日之功', '天下无难事只怕有心人', '熟能生巧功到自然成', '敏而好学不耻下问', '学而不思则罔', '思而不学则殆', '欲速则不达', '不积跬步无以至千里', '不积小流无以成江海', '温故而知新', '读书破万卷下笔如有神', '千里之行始于足下', ]; /** * 随机获取一条鼓励语 */ export function getRandomEncouragementPhrase(): string { const idx = Math.floor(Math.random() * ENCOURAGEMENT_PHRASES.length); return ENCOURAGEMENT_PHRASES[idx]; } /** * 绘制统一的每日练习每个 Section 中的 Header * @param ctx 微信小程序 Canvas 绘制上下文 * @param rect 目标 Section 的位置与宽高信息 * @param title 标题文本,如 “每日打卡” 或 “拼音每日一练” * @param options.centerMeta 是否将 “月/日/姓名” 元信息行整体水平居中(默认 false,保持原左侧布局) */ export function drawSectionHeader( ctx: RenderingContext, rect: { x: number; y: number; w: number; h: number }, title: string, options?: { centerMeta?: boolean }, ): void { const cx = rect.x + rect.w / 2; const titleY = rect.y + SECTION_PADDING_TOP + 12; const metaY = titleY + HEADER_TITLE_GAP; const metaUnderlineY = metaY + 5; const canvasCtx = ctx as any; canvasCtx.save(); canvasCtx.fillStyle = '#322E25'; canvasCtx.textAlign = 'center'; canvasCtx.textBaseline = 'middle'; // 字体样式与 dailyCheckinDraw.ts 保持一致 canvasCtx.font = '20px serif'; canvasCtx.fillText(title, cx, titleY); // 元信息行布局:原始版本以 rect.x 为基准(左侧偏移 40~248,宽 208) // 当 centerMeta=true 时,整体水平居中到 rect 中部,仅影响调用方显式开启的场景 const META_BLOCK_WIDTH = 208; const META_BASE_OFFSET = 40; const metaLeft = options?.centerMeta ? rect.x + (rect.w - META_BLOCK_WIDTH) / 2 : rect.x + META_BASE_OFFSET; const offset = (n: number) => metaLeft + (n - META_BASE_OFFSET); canvasCtx.font = '12px serif'; canvasCtx.fillText('月', offset(76), metaY); canvasCtx.fillText('日', offset(122), metaY); canvasCtx.fillText('姓名:', offset(176), metaY); canvasCtx.strokeStyle = '#8E897E'; canvasCtx.lineWidth = 1; canvasCtx.setLineDash([]); canvasCtx.beginPath(); canvasCtx.moveTo(offset(40), metaUnderlineY); canvasCtx.lineTo(offset(68), metaUnderlineY); canvasCtx.moveTo(offset(85), metaUnderlineY); canvasCtx.lineTo(offset(113), metaUnderlineY); canvasCtx.moveTo(offset(195), metaUnderlineY); canvasCtx.lineTo(offset(248), metaUnderlineY); canvasCtx.stroke(); canvasCtx.restore(); } /** * 绘制统一的每日练习每个 Section 中的 Footer * @param ctx 微信小程序 Canvas 绘制上下文 * @param rect 目标 Section 的位置与宽高信息 * @param phrase 鼓励语文本 * @param labelText 页面/天数索引文本,如 “- 1 -” 或 “第 1 天” */ export function drawSectionFooter( ctx: RenderingContext, rect: { x: number; y: number; w: number; h: number }, phrase: string, labelText: string, ): void { const cx = rect.x + rect.w / 2; const phraseY = rect.y + rect.h - SECTION_PADDING_BOTTOM - FOOTER_PHRASE_GAP; const indexY = rect.y + rect.h - SECTION_PADDING_BOTTOM - 6; const canvasCtx = ctx as any; canvasCtx.save(); canvasCtx.fillStyle = '#3E3A33'; canvasCtx.textAlign = 'center'; canvasCtx.textBaseline = 'middle'; // 字体样式与 dailyCheckinDraw.ts 保持一致 canvasCtx.font = '14px serif'; canvasCtx.fillText(phrase, cx, phraseY); canvasCtx.font = '12px serif'; canvasCtx.fillText(labelText, cx, indexY); canvasCtx.restore(); }