385 lines
12 KiB
TypeScript
385 lines
12 KiB
TypeScript
import { PAPER_SIZE } from '../constants/colors';
|
||
import { drawBaseHeader, drawBaseMiniHeader } from './baseHeaderDraw';
|
||
|
||
/**
|
||
* 基础绘制服务
|
||
* 包含Paper设置和Header绘制功能,可被所有绘制服务复用
|
||
*
|
||
* 提供功能:
|
||
* - Canvas 初始化和配置
|
||
* - Paper 尺寸设置(支持 A4 等标准尺寸)
|
||
* - Header 绘制(支持完整 Header 和迷你 Header)
|
||
* - 分割线绘制
|
||
* - 打印配置管理
|
||
*/
|
||
export class BaseDrawService {
|
||
headerType: PrintHeader = 'wechat';
|
||
canvas: WechatMiniprogram.Canvas;
|
||
ctx: RenderingContext;
|
||
options: Record<string, any>;
|
||
paperSize: PaperSize;
|
||
currentX: number;
|
||
currentY: number;
|
||
canvasWidth: number; // 逻辑像素宽度
|
||
canvasHeight: number; // 逻辑像素高度
|
||
|
||
constructor(
|
||
canvas: Canvas,
|
||
ctx: RenderingContext,
|
||
options?: Record<string, any>,
|
||
) {
|
||
options = options || {};
|
||
const { appName, appHint } = getApp().getPrintConfig();
|
||
this.canvas = canvas;
|
||
this.ctx = ctx;
|
||
this.paperSize = 'A4';
|
||
this.options = {
|
||
appName,
|
||
appHint,
|
||
title: '看数字,涂一涂',
|
||
subTitle: '找一找下面相同的数字,涂上颜色',
|
||
|
||
...options,
|
||
};
|
||
this.currentX = 0;
|
||
this.currentY = 0;
|
||
this.canvasWidth = 0;
|
||
this.canvasHeight = 0;
|
||
this.setPrintConfig();
|
||
}
|
||
|
||
setPrintConfig() {
|
||
const printConfig = getApp().getPrintConfig();
|
||
this.headerType = printConfig.header;
|
||
this.options.appName = printConfig.appName;
|
||
}
|
||
|
||
/**
|
||
* 设置Paper(逻辑像素,尺寸除以3)
|
||
*/
|
||
setPaper() {
|
||
const { ctx, canvas } = this;
|
||
const { pixelRatio: dpr } = wx.getWindowInfo();
|
||
let { width, height } = PAPER_SIZE[this.paperSize];
|
||
|
||
this.canvasWidth = width;
|
||
this.canvasHeight = height;
|
||
|
||
// 设置 canvas 为物理像素尺寸(用于高分辨率显示)
|
||
const physicalWidth = width * dpr;
|
||
const physicalHeight = height * dpr;
|
||
canvas.width = physicalWidth;
|
||
canvas.height = physicalHeight;
|
||
|
||
// 重置 transform 并 scale 到逻辑像素
|
||
ctx.setTransform(1, 0, 0, 1, 0, 0); // 重置 transform
|
||
ctx.scale(dpr, dpr); // scale 到逻辑像素,后续绘制都使用逻辑像素
|
||
|
||
this.clear();
|
||
ctx.fillStyle = '#fff';
|
||
// 使用逻辑像素尺寸填充
|
||
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||
}
|
||
|
||
/**
|
||
* 清除画布
|
||
*/
|
||
clear() {
|
||
const canvas = this.canvas;
|
||
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
}
|
||
|
||
/**
|
||
* 绘制Header(逻辑像素,尺寸除以3)
|
||
*/
|
||
async drawHeader() {
|
||
this.currentX = 25;
|
||
this.currentY = 25;
|
||
await drawBaseHeader({
|
||
canvas: this.canvas,
|
||
ctx: this.ctx,
|
||
headerType: this.headerType,
|
||
options: {
|
||
appName: this.options.appName || '涂鸦丫小程序',
|
||
appHint: this.options.appHint || '识字|识图|练字|打印',
|
||
title: this.options.title || '看数字,涂一涂',
|
||
subTitle:
|
||
this.options.subTitle || '找一找下面相同的数字,涂上颜色',
|
||
},
|
||
onHeaderDrawn: (currentY) => {
|
||
this.currentY = currentY;
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 绘制迷你Header(逻辑像素,尺寸除以3)
|
||
*/
|
||
drawMiniHeader() {
|
||
drawBaseMiniHeader({
|
||
ctx: this.ctx,
|
||
canvasWidth: this.canvasWidth,
|
||
options: {
|
||
appName: this.options.appName || '涂鸦丫小程序',
|
||
title: this.options.title || '看数字,涂一涂',
|
||
},
|
||
onHeaderDrawn: (currentY) => {
|
||
console.log('drawMiniHeader currentY', currentY);
|
||
this.currentY = currentY;
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 绘制分割线(逻辑像素,尺寸除以3)
|
||
*/
|
||
drawDivider() {
|
||
const { ctx, canvasWidth } = this;
|
||
const dividerY = this.currentY;
|
||
|
||
ctx.strokeStyle = '#000';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(24, dividerY);
|
||
ctx.lineTo(canvasWidth - 24, dividerY);
|
||
ctx.stroke();
|
||
|
||
this.currentY = dividerY + 10; // 分割线下方10px间距
|
||
}
|
||
|
||
/**
|
||
* 绘制虚线分割线(逻辑像素,尺寸除以3)
|
||
* @param y 分割线的Y坐标
|
||
* @param margin 左右边距,默认为40
|
||
* @param color 线条颜色,默认为'#999'
|
||
* @param lineWidth 线条宽度,默认为1
|
||
*/
|
||
drawDashedDivider(
|
||
y: number,
|
||
margin: number = 40,
|
||
color: string = '#999',
|
||
lineWidth: number = 1,
|
||
) {
|
||
const { ctx, canvasWidth } = this;
|
||
|
||
ctx.strokeStyle = color;
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.setLineDash([4, 4]); // 虚线
|
||
ctx.beginPath();
|
||
ctx.moveTo(margin, y);
|
||
ctx.lineTo(canvasWidth - margin, y);
|
||
ctx.stroke();
|
||
ctx.setLineDash([]); // 重置为实线
|
||
}
|
||
|
||
/**
|
||
* 绘制网格线(逻辑像素,尺寸除以3)
|
||
* @param gridStartX 网格起始X坐标
|
||
* @param gridStartY 网格起始Y坐标
|
||
* @param cellWidth 每个格子的宽度
|
||
* @param cellHeight 每个格子的高度
|
||
* @param cols 列数
|
||
* @param rows 行数
|
||
*/
|
||
drawGridLines(
|
||
gridStartX: number,
|
||
gridStartY: number,
|
||
cellWidth: number,
|
||
cellHeight: number,
|
||
cols: number,
|
||
rows: number,
|
||
): void {
|
||
const { ctx } = this;
|
||
|
||
// 在函数内部计算网格总宽度和高度
|
||
const gridWidth = cellWidth * cols;
|
||
const gridHeight = cellHeight * rows;
|
||
|
||
ctx.strokeStyle = '#000';
|
||
ctx.lineWidth = 1;
|
||
ctx.setLineDash([]); // 实线
|
||
|
||
// 绘制垂直线
|
||
for (let i = 0; i <= cols; i++) {
|
||
const x = gridStartX + i * cellWidth;
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, gridStartY);
|
||
ctx.lineTo(x, gridStartY + gridHeight);
|
||
ctx.stroke();
|
||
}
|
||
|
||
// 绘制水平线
|
||
for (let i = 0; i <= rows; i++) {
|
||
const y = gridStartY + i * cellHeight;
|
||
ctx.beginPath();
|
||
ctx.moveTo(gridStartX, y);
|
||
ctx.lineTo(gridStartX + gridWidth, y);
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 绘制圆角矩形框(逻辑像素,尺寸除以3)
|
||
* @param x 框的X坐标
|
||
* @param y 框的Y坐标
|
||
* @param width 框的宽度
|
||
* @param height 框的高度
|
||
* @param options 可选参数
|
||
* @param options.isDashed 是否为虚线,默认为false(实线)
|
||
* @param options.radius 圆角半径,默认为10
|
||
* @param options.color 线条颜色,默认为'#000'
|
||
* @param options.lineWidth 线条宽度,默认为1
|
||
*/
|
||
drawRoundedRect(
|
||
x: number,
|
||
y: number,
|
||
width: number,
|
||
height: number,
|
||
options?: {
|
||
isDashed?: boolean;
|
||
radius?: number;
|
||
color?: string;
|
||
lineWidth?: number;
|
||
},
|
||
): void {
|
||
const { ctx } = this;
|
||
const {
|
||
isDashed = false,
|
||
radius = 10,
|
||
color = '#000',
|
||
lineWidth = 1,
|
||
} = options || {};
|
||
|
||
ctx.strokeStyle = color;
|
||
ctx.lineWidth = lineWidth;
|
||
|
||
// 设置虚线或实线
|
||
if (isDashed) {
|
||
ctx.setLineDash([4, 4]); // 虚线
|
||
} else {
|
||
ctx.setLineDash([]); // 实线
|
||
}
|
||
|
||
// 绘制圆角矩形
|
||
ctx.beginPath();
|
||
ctx.moveTo(x + radius, y);
|
||
ctx.lineTo(x + width - radius, y);
|
||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||
ctx.lineTo(x + width, y + height - radius);
|
||
ctx.quadraticCurveTo(
|
||
x + width,
|
||
y + height,
|
||
x + width - radius,
|
||
y + height,
|
||
);
|
||
ctx.lineTo(x + radius, y + height);
|
||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||
ctx.lineTo(x, y + radius);
|
||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||
ctx.closePath();
|
||
ctx.stroke();
|
||
|
||
// 重置为实线(避免影响后续绘制)
|
||
ctx.setLineDash([]);
|
||
}
|
||
|
||
/**
|
||
* 绘制符号(使用路径绘制)
|
||
* @param x 符号中心X坐标
|
||
* @param y 符号中心Y坐标
|
||
* @param symbol 符号类型:'+', '-', '×', '✓'
|
||
* @param size 符号大小
|
||
*/
|
||
drawSymbol(x: number, y: number, symbol: string, size: number): void {
|
||
const { ctx } = this;
|
||
ctx.save();
|
||
ctx.translate(x, y);
|
||
|
||
const lineWidth = size * 0.15; // 线条宽度
|
||
const halfSize = size / 2;
|
||
const strokeLength = halfSize * 0.7; // 线条长度
|
||
|
||
ctx.strokeStyle = '#000';
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.lineCap = 'round';
|
||
ctx.lineJoin = 'round';
|
||
|
||
switch (symbol) {
|
||
case '+':
|
||
// 加号:横线和竖线
|
||
ctx.beginPath();
|
||
// 横线
|
||
ctx.moveTo(-strokeLength, 0);
|
||
ctx.lineTo(strokeLength, 0);
|
||
// 竖线
|
||
ctx.moveTo(0, -strokeLength);
|
||
ctx.lineTo(0, strokeLength);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
case '-':
|
||
// 减号:横线
|
||
ctx.beginPath();
|
||
ctx.moveTo(-strokeLength, 0);
|
||
ctx.lineTo(strokeLength, 0);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
case '×':
|
||
// 乘号:两条斜线
|
||
ctx.beginPath();
|
||
// 左上到右下
|
||
ctx.moveTo(-strokeLength * 0.7, -strokeLength * 0.7);
|
||
ctx.lineTo(strokeLength * 0.7, strokeLength * 0.7);
|
||
// 右上到左下
|
||
ctx.moveTo(strokeLength * 0.7, -strokeLength * 0.7);
|
||
ctx.lineTo(-strokeLength * 0.7, strokeLength * 0.7);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
case '✓':
|
||
// 对号:勾,整体更大,右侧的线更长
|
||
const checkScale = 1.2; // 对号整体放大1.2倍
|
||
ctx.beginPath();
|
||
const checkStartX = -strokeLength * 0.5 * checkScale;
|
||
const checkStartY = -strokeLength * 0.2 * checkScale;
|
||
const checkMidX = -strokeLength * 0.1 * checkScale;
|
||
const checkMidY = strokeLength * 0.3 * checkScale;
|
||
const checkEndX = strokeLength * 1.0 * checkScale; // 增加右侧长度
|
||
const checkEndY = -strokeLength * 0.4 * checkScale; // 稍微向上调整
|
||
ctx.moveTo(checkStartX, checkStartY);
|
||
ctx.lineTo(checkMidX, checkMidY);
|
||
ctx.lineTo(checkEndX, checkEndY);
|
||
ctx.stroke();
|
||
break;
|
||
|
||
default:
|
||
// 默认绘制加号
|
||
ctx.beginPath();
|
||
ctx.moveTo(-strokeLength, 0);
|
||
ctx.lineTo(strokeLength, 0);
|
||
ctx.moveTo(0, -strokeLength);
|
||
ctx.lineTo(0, strokeLength);
|
||
ctx.stroke();
|
||
}
|
||
|
||
ctx.restore();
|
||
}
|
||
|
||
/**
|
||
* 绘制圆点
|
||
*/
|
||
drawDot(
|
||
ctx: RenderingContext,
|
||
x: number,
|
||
y: number,
|
||
radius: number,
|
||
fillColor?: string,
|
||
) {
|
||
ctx.fillStyle = fillColor ?? '#93D333';
|
||
ctx.beginPath();
|
||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
}
|
||
}
|