diff --git a/miniprogram/chinesePages/handwritingSheet/draw/wordDailyCheckinDraw.ts b/miniprogram/chinesePages/handwritingSheet/draw/wordDailyCheckinDraw.ts index 5860be6..a38ffbc 100644 --- a/miniprogram/chinesePages/handwritingSheet/draw/wordDailyCheckinDraw.ts +++ b/miniprogram/chinesePages/handwritingSheet/draw/wordDailyCheckinDraw.ts @@ -144,7 +144,7 @@ export default class WordDailyCheckinDraw extends BaseDrawService { sectionIndex: number, ) { const phrase = getRandomEncouragementPhrase(); - drawSectionHeader(this.ctx, rect, '汉字每日打卡'); + drawSectionHeader(this.ctx, rect, '汉字每日打卡', { centerMeta: true }); drawSectionFooter(this.ctx, rect, phrase, `- ${sectionIndex} -`); this.drawSectionContent(rect, items); } @@ -241,6 +241,7 @@ export default class WordDailyCheckinDraw extends BaseDrawService { ctx.lineTo(startX + cellW, y2); ctx.stroke(); ctx.beginPath(); + ctx.strokeStyle = GRID_COLORS.border; ctx.setLineDash([]); const vx = startX + cellW; ctx.moveTo(vx, startY); @@ -248,37 +249,56 @@ export default class WordDailyCheckinDraw extends BaseDrawService { ctx.stroke(); ctx.restore(); - // 列分隔线 - // ctx.strokeStyle = GRID_COLORS.border; - // ctx.setLineDash([]); - // for (let c = 1; c < COLUMNS; c++) { - // const vx = startX + c * cellW; - // ctx.beginPath(); - // ctx.moveTo(vx, startY); - // ctx.lineTo(vx, yBot); - // ctx.stroke(); - // } - // 第一格:绘制拼音 if (item.pinyin) { const cx = startX + cellW / 2; - const fontSize = Math.round(rowH * 0.65); + const baseFontSize = Math.round(rowH * 0.65); ctx.save(); - ctx.font = `${fontSize}px ${fontFamilyOf(TONEOZ_PINYIN)}`; ctx.fillStyle = TRACING_COLORS.strong; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; + + // 拼音超过 4 个字母时按需缩字,避免超出单元格宽度 + let fontSize = baseFontSize; + if (item.pinyin.length > 4) { + ctx.font = `${baseFontSize}px ${fontFamilyOf(TONEOZ_PINYIN)}`; + const maxWidth = cellW * 0.9; + const measured = (ctx as any).measureText(item.pinyin).width; + if (measured > maxWidth) { + fontSize = Math.max( + 8, + Math.floor(baseFontSize * (maxWidth / measured)), + ); + } + } + ctx.font = `${fontSize}px ${fontFamilyOf(TONEOZ_PINYIN)}`; ctx.fillText(item.pinyin, cx, y2); ctx.restore(); } // 笔画格:第 2 格起,按笔画顺序展示当前进度 + // 每一格绘制整个字:已绘制的笔画为黑色,当前笔画为红色,未绘制的为浅灰色 const strokeCount = item.strokes.length; for (let k = 1; k <= strokeCount && k < COLUMNS; k++) { - const cellX = startX + cellW / 2 + k * 15; + const cellX = startX + cellW / 2 + k * 20; const currentIdx = k - 1; + const lastIdx = strokeCount - 1; - // 已绘制的历史笔画:浅灰 + // 未绘制的笔画(当前之后):浅灰 + if (currentIdx < lastIdx) { + drawStrokesRange({ + ctx, + strokes: item.strokes, + offsetX: cellX, + offsetY: startY, + width: cellW, + height: rowH, + fromIndex: currentIdx + 1, + toIndex: lastIdx, + fillStyle: TRACING_COLORS.guide, + }); + } + // 已绘制的历史笔画:黑色 if (currentIdx > 0) { drawStrokesRange({ ctx, @@ -289,7 +309,7 @@ export default class WordDailyCheckinDraw extends BaseDrawService { height: rowH, fromIndex: 0, toIndex: currentIdx - 1, - fillStyle: TRACING_COLORS.guide, + fillStyle: TRACING_COLORS.strong, }); } // 当前笔画:红色高亮 @@ -302,7 +322,7 @@ export default class WordDailyCheckinDraw extends BaseDrawService { height: rowH, fromIndex: currentIdx, toIndex: currentIdx, - fillStyle: '#D94B4B', + fillStyle: TRACING_COLORS.active, }); } } diff --git a/miniprogram/core/data/tracingStyles.ts b/miniprogram/core/data/tracingStyles.ts index 8fb590a..2b54e19 100644 --- a/miniprogram/core/data/tracingStyles.ts +++ b/miniprogram/core/data/tracingStyles.ts @@ -16,10 +16,10 @@ export const GRID_COLORS = { /** 外框、实线、垂直分隔线 — 中等绿色,清晰可见 */ border: '#7fb069', - /** 中线、虚线(田字格十字线 / 四线三格中间两线)— 浅绿色,柔和护眼 */ - middleLine: '#a8d5a8', - /** 对角线 — 极淡绿色,辅助参考线(田字格专用) */ - diagonal: '#d4f0d4', + /** 中线、虚线(田字格十字线 / 四线三格中间两线)— 喷墨可见的中绿色 */ + middleLine: '#7ec27e', + /** 对角线 — 浅绿色,辅助参考线(田字格专用),喷墨可打印 */ + diagonal: '#9bcd9b', } as const; /** 四线三格默认虚线样式 */ @@ -30,10 +30,12 @@ export const GRID_DASH = [3, 3] as const; export const TRACING_COLORS = { /** 参照字/预览字 — 深灰色,完整展示供对照 */ reference: '#555555', - /** 描红引导字 — 中性灰色,跟着描写 */ - guide: '#d0d0d0', - /** 极浅引导字 — 浅灰色,水印效果 */ - guideLight: '#e0e0e0', + /** 描红引导字 — 中灰色,跟着描写,喷墨可清晰打印 */ + guide: '#aaaaaa', + /** 极浅引导字 — 浅灰色,水印效果,喷墨可见 */ + guideLight: '#c8c8c8', /** 首笔/强调 — 近黑色,用于首个参照格 */ strong: '#1a1a1a', + /** 当前笔画高亮 — 红色,用于笔顺进度 */ + active: '#D94B4B', } as const; diff --git a/miniprogram/core/draw/dailyPracticeDrawHelper.ts b/miniprogram/core/draw/dailyPracticeDrawHelper.ts index 4e5ae56..150bf36 100644 --- a/miniprogram/core/draw/dailyPracticeDrawHelper.ts +++ b/miniprogram/core/draw/dailyPracticeDrawHelper.ts @@ -52,11 +52,13 @@ export function getRandomEncouragementPhrase(): string { * @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; @@ -74,21 +76,30 @@ export function drawSectionHeader( 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('月', rect.x + 76, metaY); - canvasCtx.fillText('日', rect.x + 122, metaY); - canvasCtx.fillText('姓名:', rect.x + 176, metaY); + 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(rect.x + 40, metaUnderlineY); - canvasCtx.lineTo(rect.x + 68, metaUnderlineY); - canvasCtx.moveTo(rect.x + 85, metaUnderlineY); - canvasCtx.lineTo(rect.x + 113, metaUnderlineY); - canvasCtx.moveTo(rect.x + 195, metaUnderlineY); - canvasCtx.lineTo(rect.x + 248, metaUnderlineY); + 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();