Files
doodle-mini/miniprogram/service/textDrawService.ts
T
2026-04-10 15:06:11 +08:00

208 lines
6.5 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.
/**
* 找一找涂色,文字涂色服务
* 根据文字列表绘制到Canvas
*/
import { BaseDrawService } from '../core/draw/baseDraw';
/**
* 计算示例区域圆的中心点
* @param canvasWidth 画布宽度(逻辑像素)
* @param circleCount 圆的数量
* @param padding 圆的间距(逻辑像素)
* @param radius 圆的半径(逻辑像素)
* @returns 圆的中心点
*/
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);
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 extends BaseDrawService {
colors: string[];
characters: string[];
constructor(
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) {
options = options || {};
super(canvas, ctx, {
title: '找一找 涂 色',
subTitle: '给文字涂上相同的颜色',
...options,
});
this.colors = ['#000'];
this.characters = ['王'];
}
async draw(list: Array<{ color: string; word: string }>) {
this.setPrintConfig();
this.colors = list.map((item) => item.color || '#000');
this.characters = list.map((item) => item.word || '日');
this.clear();
this.setPaper();
await this.drawHeader();
this.drawDivider();
this.drawLegend();
this.drawContent();
await this.drawPrintFooter();
}
/** 绘制示例
* 矩形: x、y为左上角
* 圆形:x、y为圆心
* 文字:x textAlign 为 start,文本左边缘对齐
* y textBaseline 为 alphabeticy 对应字母基线(类似左下角)
*
* 注意:所有尺寸已转换为逻辑像素(除以3)
* */
drawLegend() {
const { ctx, colors, characters } = this;
if (characters.length <= 0) return;
const legendTopY = this.currentY;
const radius = 22; // 65/3≈22
const rectWidth = 60; // 180/3=60
const rectHeight = 27; // 80/3≈27
const startX = 87 + radius; // (260+65)/3≈108
const startY = legendTopY + 10 + radius; // (30+65)/3≈32
const len = characters.length || 4;
const centers = calculateCircleCenters(
this.canvasWidth,
len,
73,
radius,
); // 220/3≈73
/** 计算每个圆之间的间距 */
const spaceWidth =
len > 3 ? (this.canvasWidth - startX * 2) / (len - 1) : 0;
ctx.moveTo(startX, startY);
colors.forEach((color: string, index: number) => {
const x = centers[index] || startX + index * spaceWidth;
const y = startY;
// 绘制圆
ctx.strokeStyle = '#000';
ctx.fillStyle = color;
ctx.lineWidth = 1; // 4/3≈1
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 = 1; // 4/3≈1
const rectangleX = x - rectWidth / 2;
const rectangleY = y + radius + 14; // 43/3≈14
ctx.strokeRect(rectangleX, rectangleY, rectWidth, rectHeight);
// 绘制字符
ctx.fillStyle = '#333';
ctx.font = 'bold 16px "Microsoft Yahei"'; // 48/3=16
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText(
characters[index],
rectangleX + rectWidth / 2,
rectangleY + rectHeight / 2,
rectWidth,
);
});
this.currentY = startY + radius + 14 + rectHeight + 24;
this.drawDivider();
}
drawContent() {
const { ctx, characters } = this;
if (characters.length <= 0) return;
const len = characters.length;
const rows = 8;
const radius = 27; // 80/3≈27
const fontSize = 24; // 72/3=24
const startY = this.currentY + 10 + radius; // 62/3≈21
const startX1 = 103 + radius; // 310/3≈103
const startX2 = 67 + radius; // 200/3≈67
const spaceWidthFrist = (this.canvasWidth - startX1 * 2) / (5 - 1);
const spaceWidthSecond = (this.canvasWidth - startX2 * 2) / (6 - 1);
const spaceHeight = 18; // 54/3=18
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 = 1; // 4/3≈1
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 已由基类的 drawDivider 替代,保留此方法以保持兼容
drawLine(linY: number) {
this.drawDivider();
}
}
export default TextDrawService;