feat: 涂色识字完善、找字涂色完成

This commit is contained in:
R524809
2026-05-09 09:56:16 +08:00
parent b85afde7c0
commit c21ac9e935
27 changed files with 328 additions and 1135 deletions
@@ -0,0 +1,207 @@
/**
* 找一找涂色,文字涂色服务
* 根据文字列表绘制到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;