// import { PAPER_SIZE } from './constant'; import { PAPER_SIZE } from '../constants/index'; import { getMiniCodeImage } from '../utils/index'; function calculateCircleCenters( canvasWidth: number, circleCount: number, padding: number, radius: number, ) { const availableWidth = canvasWidth - 2 * padding; const maxSpacing = 5.5 * radius; const centers = []; if (circleCount === 1) { // 单个圆圈直接居中 centers.push(canvasWidth / 2); } else { const requiredSpace = 2 * radius * circleCount; const spaceBetween = (availableWidth - requiredSpace) / (circleCount - 1); console.log('spaceBetween---:', spaceBetween); if (spaceBetween > maxSpacing) { // 超过最大间距时,固定间距并居中对齐 const totalWidth = 2 * radius + (circleCount - 1) * maxSpacing; const startX = (canvasWidth - totalWidth) / 2 + radius; for (let i = 0; i < circleCount; i++) { centers.push(startX + i * maxSpacing); } } else { // 正常均匀分布 const startX = padding + radius; for (let i = 0; i < circleCount; i++) { centers.push(startX + i * (spaceBetween + 2 * radius)); } } } return centers; } class TextDrawService { canvas: WechatMiniprogram.Canvas; ctx: RenderingContext; options: Record; paperSize: PaperSize; currentX: number; currentY: number; colors: string[]; characters: string[]; constructor( canvas: Canvas, ctx: RenderingContext, options?: Record, ) { options = options || {}; this.canvas = canvas; this.ctx = ctx; this.paperSize = 'A4'; this.options = { appName: '涂鸦呀', appHint: '涂色|识字|画画|认知', title: '找一找 涂 色', subTitle: '把相同的文字涂上相同的颜色', ...options, }; this.currentX = 0; this.currentY = 0; this.colors = ['#000']; this.characters = ['王']; } draw(list: Array<{ color: string; word: string }>) { this.colors = list.map((item) => item.color || '#000'); this.characters = list.map((item) => item.word || '日'); this.clear(); this.setPaper(); this.drawHeader(); this.drawLegend(); this.drawContent(); } async drawHeader() { const { canvas, ctx } = this; const { appName, appHint, title, subTitle } = this.options; this.currentX = 80; this.currentY = 80; const titleX = this.currentX + 200 + 48; const titleY = 80; const image = await getMiniCodeImage(canvas); ctx.drawImage(image, 80, 80, 200, 200); ctx.font = 'bold 64px "Microsoft Yahei"'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillText(appName, titleX, titleY); ctx.font = '48px "Microsoft Yahei"'; ctx.fillStyle = '#666'; ctx.fillText(appHint, titleX, 186); // 绘制分割线 // ctx.strokeStyle = '#888'; // ctx.lineWidth = 2; // ctx.beginPath(); // ctx.moveTo(483, 70); // ctx.lineTo(483, 70 + 140); // ctx.stroke(); ctx.font = 'bold 64px "Microsoft Yahei"'; ctx.fillStyle = '#000'; ctx.fillText(title, 1015, titleY); ctx.font = '48px "Microsoft Yahei"'; ctx.fillStyle = '#666'; ctx.fillText(subTitle, 1015, 186); this.currentY = 304; this.drawLine(this.currentY); } /** 绘制示例 * 矩形: x、y为左上角 * 圆形:x、y为圆心 * 文字:x textAlign 为 start,文本左边缘对齐 * y textBaseline 为 alphabetic,y 对应字母基线(类似左下角) * * */ drawLegend() { const { canvas, ctx, colors, characters } = this; // const { colors, characters } = options; const radius = 65; const rectWidth = 180; const rectHeight = 80; const startX = 260 + radius; // 圆形的X坐标是圆心 const startY = 304 + 26 + radius; // 圆形的Y坐标是圆心 const len = characters.length || 4; const canvasWidth = canvas.width; const centers = calculateCircleCenters(canvasWidth, len, 220, 65); /** 计算每个圆之间的间距 俩个60,一个是页面右侧边距,另一个是圆离右边距地边距*/ const spaceWidth = len > 3 ? (canvasWidth - startX * 2) / (len - 1) : 0; ctx.moveTo(startX, startY); colors.forEach((color: string, index: number) => { const x = centers[index] || startX + index * spaceWidth; // 计算圆的X坐标 const y = startY; // 计算圆的Y坐标 // 绘制圆 ctx.strokeStyle = '#000'; ctx.fillStyle = color; ctx.lineWidth = 4; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.closePath(); // 绘制长方形 ctx.fillStyle = '#fff'; // 长方形背景色 ctx.strokeStyle = '#000'; ctx.lineWidth = 4; const rectangleX = x - rectWidth / 2; // 从圆形移动一半的长方形的长 const rectangleY = y + radius + 43; // 从圆形下移一个半径,再加上43的间距 ctx.strokeRect(rectangleX, rectangleY, rectWidth, rectHeight); // 绘制长方形 // 绘制字符 ctx.fillStyle = '#333'; ctx.font = 'bold 48px "Microsoft Yahei"'; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText( characters[index], rectangleX + rectWidth / 2, rectangleY + rectHeight / 2, rectWidth, ); }); this.currentY = 612; // 计算分割线的Y坐标,距离示例图20px this.drawLine(this.currentY); } drawContent() { const { canvas, ctx, characters } = this; // const { characters } = options; const len = characters.length; const rows = 8; const radius = 80; const fontSize = 72; const startY = this.currentY + 62 + radius; const startX1 = 310 + radius; const startX2 = 200 + radius; const canvasWidth = canvas.width; const spaceWidthFrist = (canvasWidth - startX1 * 2) / (5 - 1); const spaceWidthSecond = (canvasWidth - startX2 * 2) / (6 - 1); const spaceHeight = 54; ctx.moveTo(startX1, startY); for (let i = 0; i < rows; i++) { const cols = i % 2 === 0 ? 5 : 6; for (let j = 0; j < cols; j++) { const y = startY + i * (spaceHeight + radius * 2); const x = i % 2 === 0 ? startX1 + j * spaceWidthFrist : startX2 + j * spaceWidthSecond; const character = characters[Math.floor(Math.random() * len)]; ctx.fillStyle = '#fff'; ctx.strokeStyle = '#000'; ctx.lineWidth = 4; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.fillStyle = '#333'; ctx.font = `${fontSize}px "Microsoft Yahei"`; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText(character, x, y, radius * 2); } } } drawLine(linY: number) { const { canvas, ctx } = this; ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(80, linY); ctx.lineTo(canvas.width - 80, linY); ctx.stroke(); } setPaper() { const { ctx, canvas } = this; const { pixelRatio: dpr } = wx.getWindowInfo(); // const { width, height } = { width: 595 * dpr, height: 842 * dpr }; let { width, height } = PAPER_SIZE[this.paperSize]; width = width * dpr; height = height * dpr; canvas.width = width; canvas.height = height; this.clear(); ctx.fillStyle = '#fff'; // 设置背景色为白色 ctx.fillRect(0, 0, width, height); } clear() { const canvas = this.canvas; this.ctx.clearRect(0, 0, canvas.width, canvas.height); } } export default TextDrawService;