// Painter 的 Canvas 2D 实现。Cocos 版本换成 Graphics + Label 即可,BoardView 不用改。 import type { Rect } from '../src/layout'; import type { Painter } from '../src/painter'; /** * 盘面字体栈,与 web/style.css 的 --font 完全一致(tests/theme.test.ts 校验)。 * ui-rounded 让 iOS / macOS 直接拿到系统圆体;其余按平台回退到 PingFang / Noto。 */ export const BOARD_FONT_FAMILY = 'ui-rounded, "SF Pro Rounded", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", system-ui, -apple-system, "Segoe UI", sans-serif'; export class CanvasPainter implements Painter { private readonly ctx: CanvasRenderingContext2D; constructor(ctx: CanvasRenderingContext2D) { this.ctx = ctx; } private path(r: Rect, radius: number): void { const ctx = this.ctx; const rad = Math.max(0, Math.min(radius, r.w / 2, r.h / 2)); ctx.beginPath(); if (typeof ctx.roundRect === 'function') { ctx.roundRect(r.x, r.y, r.w, r.h, rad); } else { // 老 WebView 兜底 const { x, y, w, h } = r; ctx.moveTo(x + rad, y); ctx.arcTo(x + w, y, x + w, y + h, rad); ctx.arcTo(x + w, y + h, x, y + h, rad); ctx.arcTo(x, y + h, x, y, rad); ctx.arcTo(x, y, x + w, y, rad); ctx.closePath(); } } fillRoundRect(r: Rect, radius: number, color: string, alpha = 1): void { if (alpha <= 0 || r.w <= 0 || r.h <= 0) return; const ctx = this.ctx; ctx.globalAlpha = Math.min(1, alpha); ctx.fillStyle = color; this.path(r, radius); ctx.fill(); ctx.globalAlpha = 1; } strokeRoundRect(r: Rect, radius: number, color: string, lineWidth: number, alpha = 1): void { if (alpha <= 0) return; const ctx = this.ctx; ctx.globalAlpha = Math.min(1, alpha); ctx.strokeStyle = color; ctx.lineWidth = lineWidth; this.path(r, radius); ctx.stroke(); ctx.globalAlpha = 1; } fillCircle(cx: number, cy: number, radius: number, color: string, alpha = 1): void { if (alpha <= 0) return; const ctx = this.ctx; ctx.globalAlpha = Math.min(1, alpha); ctx.fillStyle = color; ctx.beginPath(); ctx.arc(cx, cy, radius, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; } /** 数字 800、汉字 700(docs/design.md 盘面字阶);不加粗时 500 */ text(s: string, cx: number, cy: number, size: number, color: string, bold = false, alpha = 1): void { if (alpha <= 0) return; const ctx = this.ctx; ctx.globalAlpha = Math.min(1, alpha); ctx.fillStyle = color; ctx.font = `${bold ? (/^\d+$/.test(s) ? '800' : '700') : '500'} ${size}px ${BOARD_FONT_FAMILY}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(s, cx, cy + size * 0.04); ctx.globalAlpha = 1; } }