271 lines
8.6 KiB
TypeScript
271 lines
8.6 KiB
TypeScript
// 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<string, any>;
|
||
paperSize: PaperSize;
|
||
currentX: number;
|
||
currentY: number;
|
||
colors: string[];
|
||
characters: string[];
|
||
|
||
constructor(
|
||
canvas: Canvas,
|
||
ctx: RenderingContext,
|
||
options?: Record<string, any>,
|
||
) {
|
||
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;
|