feat:架构代码优化
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# Service 目录绘制服务改造完成总结
|
||||
|
||||
## 改造目标
|
||||
|
||||
将所有绘制服务统一使用 `baseDraw.ts` 和 `baseHeaderDraw.ts` 作为基类,提取公共部分并统一 Header 绘制。
|
||||
|
||||
## 完成的工作
|
||||
|
||||
### 1. ✅ 统一基础服务类
|
||||
|
||||
所有绘制服务现在都继承自 `BaseDrawService`(位于 `service/baseDraw.ts`):
|
||||
|
||||
- ✅ `TextDrawService` - 文字涂色服务
|
||||
- ✅ `ShapeDrawService` - 图形涂色服务
|
||||
- ✅ `FindWordDrawService` - 找字涂色服务
|
||||
- ✅ `WordDrawService` - 田字格练字服务
|
||||
|
||||
### 2. ✅ 统一 Header 绘制
|
||||
|
||||
所有服务使用统一的 Header 绘制功能(位于 `service/baseHeaderDraw.ts`):
|
||||
|
||||
- `drawBaseHeader()` - 完整 Header(包含 Logo、应用名称、标题、副标题)
|
||||
- `drawBaseMiniHeader()` - 迷你 Header(简化版本)
|
||||
|
||||
### 3. ✅ 统一使用逻辑像素
|
||||
|
||||
所有服务现在使用逻辑像素(尺寸除以3),与数学模块保持一致:
|
||||
|
||||
- Canvas 尺寸通过 `setPaper()` 统一处理 DPR(device pixel ratio)
|
||||
- 所有绘制代码中的硬编码尺寸都已转换为逻辑像素
|
||||
- 坐标计算统一使用 `this.canvasWidth` 和 `this.canvasHeight`
|
||||
|
||||
### 4. ✅ 清理重复代码
|
||||
|
||||
删除了以下重复方法,统一使用基类实现:
|
||||
|
||||
- `setPrintConfig()` - 统一在基类中实现
|
||||
- `setPaper()` - 统一在基类中实现
|
||||
- `clear()` - 统一在基类中实现
|
||||
- `drawHeader()` / `drawMiniHeader()` - 统一在基类中实现
|
||||
- `drawDivider()` - 统一在基类中实现
|
||||
|
||||
## 改造详情
|
||||
|
||||
### TextDrawService
|
||||
|
||||
**改造内容:**
|
||||
|
||||
- 继承 `BaseDrawService`
|
||||
- 移除自定义的 `setPaper()`, `clear()`, `drawHeader()`, `drawMiniHeader()`
|
||||
- 所有尺寸转换为逻辑像素:
|
||||
- 半径:80 → 27
|
||||
- 字体:72px → 24px, 48px → 16px
|
||||
- 边距:80 → 27, 220 → 73
|
||||
- 坐标:200 → 67, 304 → 101
|
||||
|
||||
**关键方法:**
|
||||
|
||||
- `drawLegend()` - 绘制示例区域
|
||||
- `drawContent()` - 绘制内容区域
|
||||
|
||||
### ShapeDrawService
|
||||
|
||||
**改造内容:**
|
||||
|
||||
- 继承 `BaseDrawService`
|
||||
- 移除自定义的绘制方法
|
||||
- 所有尺寸转换为逻辑像素:
|
||||
- 图形大小:200 → 67, 180 → 60
|
||||
- 字体:36px → 12px
|
||||
- 间距:120 → 40, 160 → 53
|
||||
|
||||
**关键方法:**
|
||||
|
||||
- `drawLegend()` - 绘制示例区域
|
||||
- `drawContent()` - 绘制内容区域
|
||||
|
||||
### FindWordDrawService
|
||||
|
||||
**改造内容:**
|
||||
|
||||
- 继承 `BaseDrawService`
|
||||
- 移除自定义的绘制方法
|
||||
- 所有尺寸转换为逻辑像素:
|
||||
- 半径:80 → 27
|
||||
- 字体:72px → 24px
|
||||
- 模板坐标转换:`pos.x / 3`, `pos.y / 3`
|
||||
|
||||
**关键方法:**
|
||||
|
||||
- `drawContent()` - 绘制找字内容(中心大字 + 四周字符)
|
||||
|
||||
**特殊处理:**
|
||||
|
||||
- `getPositionsFromTemplate()` 函数中增加了坐标转换逻辑,将模板中的原始像素坐标转换为逻辑像素
|
||||
|
||||
### WordDrawService
|
||||
|
||||
**改造内容:**
|
||||
|
||||
- 继承 `BaseDrawService`
|
||||
- 移除自定义的绘制方法
|
||||
- 所有尺寸转换为逻辑像素:
|
||||
- 田字格大小:140 → 47
|
||||
- 边距:120 → 40, 50 → 17
|
||||
- 间距:24 → 8, 36 → 12
|
||||
|
||||
**关键方法:**
|
||||
|
||||
- `drawLayout()` - 绘制页眉和基础布局
|
||||
- `drawContent()` - 绘制田字格和练字内容
|
||||
- `drawPracticeContent()` - 绘制练习内容
|
||||
- `getMaxGridLayout()` - 计算最大网格布局
|
||||
|
||||
## 架构优势
|
||||
|
||||
1. ✅ **代码复用**:公共逻辑集中在 `BaseDrawService` 基类中
|
||||
2. ✅ **统一 Header**:所有服务使用相同的 Header 绘制逻辑
|
||||
3. ✅ **统一像素标准**:所有服务使用逻辑像素,保持一致
|
||||
4. ✅ **易于维护**:修改 Header 或基础功能只需修改一个地方
|
||||
5. ✅ **类型安全**:TypeScript 类型支持完整
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 坐标转换
|
||||
|
||||
所有硬编码的尺寸值都已转换为逻辑像素(除以3):
|
||||
|
||||
```typescript
|
||||
// 原始像素 → 逻辑像素
|
||||
const radius = 27; // 80/3≈27
|
||||
const fontSize = 24; // 72/3=24
|
||||
const margin = 40; // 120/3=40
|
||||
```
|
||||
|
||||
### 模板坐标转换
|
||||
|
||||
`FindWordDrawService` 中使用的 `POSITION_TEMPLATES` 坐标是基于原始像素的,需要在使用时转换:
|
||||
|
||||
```typescript
|
||||
const x = centerX + pos.x / 3; // 转换为逻辑像素
|
||||
const y = centerY + pos.y / 3; // 转换为逻辑像素
|
||||
```
|
||||
|
||||
### Canvas 尺寸
|
||||
|
||||
所有服务现在统一使用:
|
||||
|
||||
- `this.canvasWidth` - 逻辑像素宽度
|
||||
- `this.canvasHeight` - 逻辑像素高度
|
||||
|
||||
这些值在 `setPaper()` 中设置,已经处理了 DPR。
|
||||
|
||||
## 向后兼容性
|
||||
|
||||
- ✅ `drawServiceFactory.ts` 接口无需修改
|
||||
- ✅ 所有服务的公共接口保持不变
|
||||
- ✅ 只改变了内部实现方式
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以考虑将 `findWordTemplate.ts` 中的坐标模板也转换为逻辑像素,避免运行时转换
|
||||
2. 可以考虑创建更多的基础绘制工具函数(如绘制表格、绘制网格等)
|
||||
3. 可以考虑支持更多 Paper 尺寸(目前仅支持 A4)
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. 验证所有绘制服务功能正常
|
||||
2. 验证 Header 正确显示
|
||||
3. 验证尺寸和比例正确
|
||||
4. 验证不同 Header 类型(wechat, LogoImage, noLogoImage, minimal)
|
||||
5. 验证打印输出质量
|
||||
@@ -0,0 +1,218 @@
|
||||
# 绘制服务重构总结
|
||||
|
||||
## 重构目标
|
||||
|
||||
基于 `baseDraw.ts` 和 `baseHeaderDraw.ts` 统一所有绘制服务的公共部分,提取并统一 Header 绘制功能。
|
||||
|
||||
## 完成的工作
|
||||
|
||||
### 1. ✅ 统一基础服务类
|
||||
|
||||
所有绘制服务现在都继承自 `BaseDrawService`(位于 `service/baseDraw.ts`):
|
||||
|
||||
- ✅ `AdditionDraw` - 加减法计算
|
||||
- ✅ `CompareDraw` - 数一数比大小
|
||||
- ✅ `CountMatchDraw` - 数一数连一连
|
||||
- ✅ `CountingSelectDraw` - 数一数选一选/填一填
|
||||
- ✅ `MissingNumberDraw` - 填上缺少的数字
|
||||
- ✅ `NumberColorDraw` - 按数字涂颜色
|
||||
- ✅ `NumberDecomposeDraw` - 10以内数的分与合
|
||||
- ✅ `NumberFindDraw` - 数字涂色(找数字、写数字)
|
||||
|
||||
### 2. ✅ 统一 Header 绘制
|
||||
|
||||
所有服务使用统一的 Header 绘制功能(位于 `service/baseHeaderDraw.ts`):
|
||||
|
||||
- `drawBaseHeader()` - 完整 Header(包含 Logo、应用名称、标题、副标题)
|
||||
- `drawBaseMiniHeader()` - 迷你 Header(简化版本)
|
||||
|
||||
### 3. ✅ 修复相关导入
|
||||
|
||||
- ✅ 修复 `base/pageMixin.ts` 中的导入路径
|
||||
- 从 `../mathPages/service/baseMathDraw` 改为 `../service/baseDraw`
|
||||
|
||||
### 4. ✅ 清理旧文件
|
||||
|
||||
- ✅ 删除 `mathPages/shared/service/baseMathDraw.ts`(已被 `BaseDrawService` 替代)
|
||||
- ✅ 删除 `mathPages/shared/service/mathHeaderDraw.ts`(已被 `baseHeaderDraw.ts` 替代)
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 基础服务类 (`BaseDrawService`)
|
||||
|
||||
提供以下公共功能:
|
||||
|
||||
```typescript
|
||||
class BaseDrawService {
|
||||
// Canvas 和上下文
|
||||
canvas: Canvas;
|
||||
ctx: RenderingContext;
|
||||
|
||||
// 配置
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
headerType: PrintHeader;
|
||||
|
||||
// 坐标管理
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
canvasWidth: number;
|
||||
canvasHeight: number;
|
||||
|
||||
// 公共方法
|
||||
setPrintConfig(); // 设置打印配置
|
||||
setPaper(); // 设置 Paper 尺寸
|
||||
clear(); // 清除画布
|
||||
drawHeader(); // 绘制完整 Header
|
||||
drawMiniHeader(); // 绘制迷你 Header
|
||||
drawDivider(); // 绘制分割线
|
||||
}
|
||||
```
|
||||
|
||||
### Header 绘制 (`baseHeaderDraw.ts`)
|
||||
|
||||
提供统一的 Header 绘制函数:
|
||||
|
||||
- `drawBaseHeader()` - 支持多种 Header 类型:
|
||||
|
||||
- `wechat` - 微信小程序码(默认)
|
||||
- `LogoImage` - Logo 图片
|
||||
- `noLogoImage` - 无 Logo
|
||||
- `minimal` - 极简模式
|
||||
|
||||
- `drawBaseMiniHeader()` - 迷你版本,居中显示
|
||||
|
||||
### 具体服务类
|
||||
|
||||
每个具体服务类:
|
||||
|
||||
1. **继承 `BaseDrawService`**
|
||||
2. **实现 `draw()` 方法**,通常包含以下步骤:
|
||||
|
||||
```typescript
|
||||
async draw(data: any) {
|
||||
// 1. 设置配置
|
||||
this.setPrintConfig();
|
||||
|
||||
// 2. 清除并设置 Paper
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 3. 绘制 Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
this.drawMiniHeader();
|
||||
}
|
||||
|
||||
// 4. 绘制分割线
|
||||
this.drawDivider();
|
||||
|
||||
// 5. 绘制内容(调用 ContentDraw 函数)
|
||||
await drawXXXContent({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
data: data,
|
||||
canvasWidth: this.canvasWidth,
|
||||
startY: this.currentY,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
3. **使用 ContentDraw 函数**绘制具体内容(例如 `drawAdditionContent`)
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
service/
|
||||
├── baseDraw.ts # 基础绘制服务类 ✅
|
||||
├── baseHeaderDraw.ts # Header 绘制函数 ✅
|
||||
└── ...
|
||||
|
||||
mathPages/shared/service/
|
||||
├── additionDraw.ts # 加减法绘制服务 ✅
|
||||
├── compareDraw.ts # 比较绘制服务 ✅
|
||||
├── countMatchDraw.ts # 连线绘制服务 ✅
|
||||
├── countingSelectDraw.ts # 选择绘制服务 ✅
|
||||
├── missingNumberDraw.ts # 缺失数字绘制服务 ✅
|
||||
├── numberColorDraw.ts # 数字涂色绘制服务 ✅
|
||||
├── numberDecomposeDraw.ts # 分解绘制服务 ✅
|
||||
├── numberFindDraw.ts # 数字查找绘制服务 ✅
|
||||
├── additionContentDraw.ts # 加减法内容绘制
|
||||
├── compareContentDraw.ts # 比较内容绘制
|
||||
├── ... # 其他 ContentDraw 函数
|
||||
└── (已删除)
|
||||
├── baseMathDraw.ts # ❌ 已删除
|
||||
└── mathHeaderDraw.ts # ❌ 已删除
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 创建新的绘制服务
|
||||
|
||||
```typescript
|
||||
import { BaseDrawService } from '../../../service/baseDraw';
|
||||
import { drawMyContent } from './myContentDraw';
|
||||
|
||||
class MyDraw extends BaseDrawService {
|
||||
myData: any;
|
||||
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
super(canvas, ctx, options);
|
||||
this.myData = null;
|
||||
}
|
||||
|
||||
async draw(myData: any) {
|
||||
if (!myData) return;
|
||||
|
||||
this.setPrintConfig();
|
||||
this.myData = myData;
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 绘制 Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
this.drawMiniHeader();
|
||||
}
|
||||
|
||||
// 绘制内容
|
||||
this.drawDivider();
|
||||
await drawMyContent({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
data: this.myData,
|
||||
canvasWidth: this.canvasWidth,
|
||||
startY: this.currentY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDraw;
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. ✅ **代码复用**:公共逻辑集中在基类中
|
||||
2. ✅ **统一 Header**:所有服务使用相同的 Header 绘制逻辑
|
||||
3. ✅ **易于维护**:修改 Header 只需修改一个地方
|
||||
4. ✅ **类型安全**:TypeScript 类型支持完整
|
||||
5. ✅ **扩展性好**:新增服务只需继承基类并实现 `draw()` 方法
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **ContentDraw 函数**:具体内容绘制函数通常以 `drawXXXContent` 命名,接收参数包括 `canvas`、`ctx`、数据、`canvasWidth`、`startY` 等
|
||||
2. **坐标管理**:使用 `this.currentY` 管理垂直坐标,ContentDraw 函数需要更新该值
|
||||
3. **Header 类型**:通过 `this.headerType` 判断使用完整 Header 还是迷你 Header
|
||||
4. **逻辑像素**:所有尺寸都已除以 3,使用逻辑像素
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 考虑将 ContentDraw 函数也提取到 `service/` 目录下的统一位置
|
||||
2. 可以考虑创建更多的基础绘制工具函数(如绘制表格、绘制网格等)
|
||||
3. 可以考虑支持更多 Paper 尺寸(目前仅支持 A4)
|
||||
@@ -0,0 +1,149 @@
|
||||
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间距
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { getMiniCodeImage, getImage } from '../utils/index';
|
||||
|
||||
/**
|
||||
* 绘制数学模块页眉的参数接口(尺寸除以3)
|
||||
*/
|
||||
interface drawBaseHeaderParams {
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
headerType: PrintHeader;
|
||||
options: {
|
||||
appName: string;
|
||||
appHint: string;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
};
|
||||
onHeaderDrawn?: (currentY: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数学模块完整页眉(尺寸除以3)
|
||||
*/
|
||||
export async function drawBaseHeader({
|
||||
canvas,
|
||||
ctx,
|
||||
headerType,
|
||||
options,
|
||||
onHeaderDrawn,
|
||||
}: drawBaseHeaderParams): Promise<void> {
|
||||
const { appName, appHint, title, subTitle } = options;
|
||||
|
||||
let titleX = 108; // 约109.33
|
||||
const titleY = 25; // 约26.67
|
||||
|
||||
const logoX = 24; // 约26.67
|
||||
const logoY = 20; // 20
|
||||
const logoWidth = 65; // 约66.67
|
||||
const logoHeight = 65; // 约66.67
|
||||
|
||||
// 根据 headerType 绘制 Logo 或调整标题位置
|
||||
switch (headerType) {
|
||||
case 'LogoImage': {
|
||||
const image = await getImage(
|
||||
canvas,
|
||||
'/assets/imgs/doodle-logo.png',
|
||||
);
|
||||
ctx.drawImage(image, logoX, logoY, logoWidth, logoHeight);
|
||||
break;
|
||||
}
|
||||
case 'noLogoImage': {
|
||||
titleX = 40;
|
||||
break;
|
||||
}
|
||||
case 'minimal': {
|
||||
titleX = 40;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const image = await getMiniCodeImage(canvas);
|
||||
ctx.drawImage(image, logoX, logoY, logoWidth, logoHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制应用名称(字体大小除以3)
|
||||
ctx.font = 'bold 22px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(appName, titleX, titleY);
|
||||
|
||||
// 绘制应用提示(字体大小除以3)
|
||||
ctx.font = '16px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(appHint, titleX, 66);
|
||||
|
||||
// 绘制标题(字体大小除以3)
|
||||
ctx.font = 'bold 22px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillText(title, 280, titleY);
|
||||
|
||||
// 绘制副标题(字体大小除以3)
|
||||
ctx.font = '16px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(subTitle, 280, 66);
|
||||
|
||||
// 调用回调函数
|
||||
if (onHeaderDrawn) {
|
||||
onHeaderDrawn(104);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数学模块迷你页眉的参数接口(尺寸除以3)
|
||||
*/
|
||||
interface drawBaseMiniHeaderParams {
|
||||
// canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: {
|
||||
appName: string;
|
||||
title: string;
|
||||
};
|
||||
canvasWidth: number; // 逻辑像素宽度(已除以3)
|
||||
onHeaderDrawn?: (currentY: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数学模块迷你页眉(尺寸除以3)
|
||||
*/
|
||||
export function drawBaseMiniHeader({
|
||||
ctx,
|
||||
options,
|
||||
canvasWidth,
|
||||
onHeaderDrawn,
|
||||
}: drawBaseMiniHeaderParams): void {
|
||||
const { appName, title } = options;
|
||||
const titleY = 46;
|
||||
const centerX = canvasWidth / 2;
|
||||
|
||||
// 字体大小除以3
|
||||
ctx.font = 'bold 24px "Microsoft Yahei"'; // 64/3
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(appName + ' ' + title, centerX, titleY);
|
||||
|
||||
// 调用回调函数,传递除以3后的currentY
|
||||
if (onHeaderDrawn) {
|
||||
onHeaderDrawn(60); // 约66.67
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
import { PAPER_SIZE } from '../constants/colors';
|
||||
import {
|
||||
drawHeader as drawHeaderCommon,
|
||||
drawMiniHeader as drawMiniHeaderCommon,
|
||||
} from './headerDrawService';
|
||||
import { BaseDrawService } from './baseDraw';
|
||||
import { POSITION_TEMPLATES } from './findWordTemplate';
|
||||
|
||||
// ==================== Debug 开关 ====================
|
||||
@@ -35,13 +31,11 @@ function selectTemplate(): { templateIndex: number } {
|
||||
/**
|
||||
* 从指定模板中获取位置
|
||||
* @param templateIndex 模板索引
|
||||
* @param centerX 中心X坐标
|
||||
* @param centerY 中心Y坐标
|
||||
* @param padding 边距
|
||||
* @param canvasWidth 画布宽度
|
||||
* @param canvasHeight 画布高度
|
||||
* @param radius 字符圆半径
|
||||
* @returns 位置数组
|
||||
* @param centerX 中心X坐标(逻辑像素)
|
||||
* @param centerY 中心Y坐标(逻辑像素)
|
||||
* @returns 位置数组(逻辑像素)
|
||||
*
|
||||
* 注意:POSITION_TEMPLATES 中的坐标是基于原始像素的,需要转换为逻辑像素(除以3)
|
||||
*/
|
||||
function getPositionsFromTemplate(
|
||||
templateIndex: number,
|
||||
@@ -50,25 +44,18 @@ function getPositionsFromTemplate(
|
||||
): Array<{ x: number; y: number }> {
|
||||
const template = POSITION_TEMPLATES[templateIndex];
|
||||
|
||||
// 转换为绝对坐标并检查边界
|
||||
// 转换为绝对坐标(模板坐标除以3转换为逻辑像素)
|
||||
const positions: Array<{ x: number; y: number }> = [];
|
||||
for (const pos of template) {
|
||||
const x = centerX + pos.x;
|
||||
const y = centerY + pos.y;
|
||||
const x = centerX + pos.x / 3; // 转换为逻辑像素
|
||||
const y = centerY + pos.y / 3; // 转换为逻辑像素
|
||||
|
||||
positions.push({ x, y });
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
class FindWordDrawService {
|
||||
headerType: PrintHeader = 'wechat';
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
class FindWordDrawService extends BaseDrawService {
|
||||
colors: string[];
|
||||
characters: string[];
|
||||
debug: boolean = false; // Debug模式开关
|
||||
@@ -79,29 +66,15 @@ class FindWordDrawService {
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
options = options || {};
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦丫小程序',
|
||||
appHint: '识字|识图|练字|打印',
|
||||
super(canvas, ctx, {
|
||||
title: '找一找 涂 色',
|
||||
subTitle: '找出相同的文字涂色',
|
||||
...options,
|
||||
};
|
||||
});
|
||||
// 从options中读取debug参数,如果没有则使用全局DEBUG常量
|
||||
this.debug = options.debug === true || DEBUG;
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
this.colors = ['#000'];
|
||||
this.characters = ['王'];
|
||||
this.setPrintConfig();
|
||||
}
|
||||
|
||||
setPrintConfig() {
|
||||
const printConfig = getApp().getPrintConfig();
|
||||
this.headerType = printConfig.header;
|
||||
this.options.appName = printConfig.appName;
|
||||
}
|
||||
|
||||
async draw(list: Array<{ color: string; word: string }>) {
|
||||
@@ -111,6 +84,8 @@ class FindWordDrawService {
|
||||
this.characters = list.map((item) => item.word || '日');
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 绘制Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
@@ -118,64 +93,30 @@ class FindWordDrawService {
|
||||
}
|
||||
|
||||
// 找字模板没有 drawLegend 部分
|
||||
this.drawDivider();
|
||||
this.drawContent();
|
||||
}
|
||||
|
||||
async drawHeader() {
|
||||
this.currentX = 80;
|
||||
this.currentY = 80;
|
||||
await drawHeaderCommon({
|
||||
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;
|
||||
this.drawLine(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
drawMiniHeader() {
|
||||
drawMiniHeaderCommon({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
options: {
|
||||
appName: this.options.appName || '涂鸦丫小程序',
|
||||
title: this.options.title || '找一找 涂 色',
|
||||
},
|
||||
onHeaderDrawn: (currentY) => {
|
||||
this.currentY = currentY;
|
||||
this.drawLine(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
drawContent() {
|
||||
const { canvas, ctx, characters, colors } = this;
|
||||
const { ctx, characters, colors } = this;
|
||||
if (characters.length <= 0) return;
|
||||
|
||||
// 第一个字作为中心大字
|
||||
const firstChar = characters[0];
|
||||
const firstColor = colors[0];
|
||||
|
||||
// 计算内容区域
|
||||
const contentTop = this.currentY + 50;
|
||||
const contentBottom = canvas.height - 80;
|
||||
// 计算内容区域(逻辑像素)
|
||||
const contentTop = this.currentY + 17; // 50/3≈17
|
||||
const contentBottom = this.canvasHeight - 27; // 80/3≈27
|
||||
const contentHeight = contentBottom - contentTop;
|
||||
|
||||
// 中心大字的参数
|
||||
const centerX = canvas.width / 2;
|
||||
const centerY = contentTop + contentHeight / 2; // 内容区域垂直居中
|
||||
const centerX = this.canvasWidth / 2;
|
||||
const centerY = contentTop + contentHeight / 2;
|
||||
|
||||
// 普通圆的参数(和textDrawService一致)
|
||||
const radius = 80;
|
||||
const fontSize = 72;
|
||||
// 普通圆的参数(逻辑像素)
|
||||
const radius = 27; // 80/3≈27
|
||||
const fontSize = 24; // 72/3=24
|
||||
|
||||
// 先选择模板,获取模板的实际位置数量
|
||||
const { templateIndex } = selectTemplate();
|
||||
@@ -242,12 +183,12 @@ class FindWordDrawService {
|
||||
// 绘制四周的字符
|
||||
positions.forEach((pos, index) => {
|
||||
const item = charsToDraw[index];
|
||||
const y = pos.y; // 已经是绝对坐标,不需要再加contentTop
|
||||
const y = pos.y; // 已经是绝对坐标
|
||||
|
||||
// 绘制圆
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.lineWidth = 1; // 4/3≈1
|
||||
ctx.beginPath();
|
||||
ctx.arc(pos.x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
@@ -265,35 +206,9 @@ class FindWordDrawService {
|
||||
});
|
||||
}
|
||||
|
||||
// drawLine 已由基类的 drawDivider 替代,保留此方法以保持兼容
|
||||
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();
|
||||
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);
|
||||
this.drawDivider();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +1,8 @@
|
||||
import { PAPER_SIZE } from '../constants/colors';
|
||||
import { BaseDrawService } from './baseDraw';
|
||||
import { ShapeCard } from '../constants/shapes';
|
||||
import { drawShape } from './drawShape';
|
||||
import {
|
||||
drawHeader as drawHeaderCommon,
|
||||
drawMiniHeader as drawMiniHeaderCommon,
|
||||
} from './headerDrawService';
|
||||
|
||||
/**
|
||||
* 计算图形在画布上的位置,避免重叠
|
||||
* @param canvasWidth 画布宽度
|
||||
* @param canvasHeight 画布高度
|
||||
* @param shapeCount 图形数量
|
||||
* @param shapeSize 图形大小
|
||||
* @returns 图形位置数组
|
||||
*/
|
||||
function calculateShapePositions(
|
||||
canvasWidth: number,
|
||||
canvasHeight: number,
|
||||
shapeCount: number,
|
||||
shapeSize: number,
|
||||
) {
|
||||
const positions = [];
|
||||
const padding = 80;
|
||||
const minSpacing = shapeSize * 1.5; // 最小间距为图形大小的1.5倍
|
||||
|
||||
const availableWidth = canvasWidth - 2 * padding;
|
||||
const availableHeight = canvasHeight - 2 * padding;
|
||||
|
||||
// 计算网格布局
|
||||
const cols = Math.ceil(Math.sqrt(shapeCount));
|
||||
const rows = Math.ceil(shapeCount / cols);
|
||||
|
||||
const cellWidth = availableWidth / cols;
|
||||
const cellHeight = availableHeight / rows;
|
||||
|
||||
for (let i = 0; i < shapeCount; i++) {
|
||||
const row = Math.floor(i / cols);
|
||||
const col = i % cols;
|
||||
|
||||
// 在单元格内随机位置
|
||||
const x =
|
||||
padding +
|
||||
col * cellWidth +
|
||||
(cellWidth - shapeSize) / 2 +
|
||||
(Math.random() - 0.5) * (cellWidth - shapeSize) * 0.3;
|
||||
const y =
|
||||
padding +
|
||||
row * cellHeight +
|
||||
(cellHeight - shapeSize) / 2 +
|
||||
(Math.random() - 0.5) * (cellHeight - shapeSize) * 0.3;
|
||||
|
||||
positions.push({ x, y });
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
class ShapeDrawService {
|
||||
headerType: PrintHeader;
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
class ShapeDrawService extends BaseDrawService {
|
||||
shapes: ShapeCard[];
|
||||
|
||||
constructor(
|
||||
@@ -72,116 +11,61 @@ class ShapeDrawService {
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
options = options || {};
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦丫小程序',
|
||||
appHint: '涂色|识字|画画|打印',
|
||||
super(canvas, ctx, {
|
||||
title: '找一找 涂 色',
|
||||
subTitle: '给图形涂上相同的颜色',
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
});
|
||||
this.shapes = [];
|
||||
this.headerType = 'wechat'; // 默认值
|
||||
this.setPrintConfig();
|
||||
}
|
||||
|
||||
setPrintConfig() {
|
||||
const printConfig = getApp().getPrintConfig();
|
||||
this.headerType = printConfig.header;
|
||||
this.options.appName = printConfig.appName;
|
||||
}
|
||||
|
||||
draw(shapes: ShapeCard[]) {
|
||||
async draw(shapes: ShapeCard[]) {
|
||||
this.setPrintConfig();
|
||||
this.shapes = shapes.slice(0, 6); // 最多6个图形
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 绘制Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
this.drawHeader();
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
this.drawMiniHeader();
|
||||
await this.drawMiniHeader();
|
||||
}
|
||||
|
||||
this.drawDivider();
|
||||
this.drawLegend();
|
||||
this.drawContent();
|
||||
}
|
||||
|
||||
async drawHeader() {
|
||||
this.currentX = 80;
|
||||
this.currentY = 80;
|
||||
await drawHeaderCommon({
|
||||
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;
|
||||
this.drawLine(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
drawMiniHeader() {
|
||||
drawMiniHeaderCommon({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
options: {
|
||||
appName: this.options.appName || '涂鸦丫小程序',
|
||||
title: this.options.title || '找一找 涂 色',
|
||||
},
|
||||
onHeaderDrawn: (currentY) => {
|
||||
this.currentY = currentY;
|
||||
this.drawLine(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
drawLegend() {
|
||||
const { canvas, ctx, shapes } = this;
|
||||
const { ctx, shapes } = this;
|
||||
if (shapes.length <= 0) return;
|
||||
|
||||
this.currentY = this.headerType === 'minimal' ? 200 : 304;
|
||||
const shapeSize = 200;
|
||||
const rectWidth = 180;
|
||||
const rectHeight = 80;
|
||||
// 固定图例的Y位置,不依赖shapeSize
|
||||
const startY = this.currentY + 125; // 固定距离,不依赖shapeSize
|
||||
// 逻辑像素尺寸(原始尺寸除以3)
|
||||
this.currentY = this.headerType === 'minimal' ? 75 : 110; // 200/3≈67, 304/3≈101
|
||||
const shapeSize = 67; // 200/3≈67
|
||||
const rectWidth = 60; // 180/3=60
|
||||
const rectHeight = 27; // 80/3≈27
|
||||
const startY = this.currentY + 42; // 125/3≈42
|
||||
const len = shapes.length;
|
||||
const canvasWidth = canvas.width;
|
||||
|
||||
// 计算示例图形的间距
|
||||
const totalWidth = len * shapeSize + (len - 1) * 40;
|
||||
const startX = (canvasWidth - totalWidth) / 2 + shapeSize / 2;
|
||||
const totalWidth = len * shapeSize + (len - 1) * 13; // 40/3≈13
|
||||
const startX = (this.canvasWidth - totalWidth) / 2 + shapeSize / 2;
|
||||
|
||||
// 绘制所有图形
|
||||
shapes.forEach((shape: ShapeCard, index: number) => {
|
||||
const x = startX + index * (shapeSize + 40);
|
||||
const x = startX + index * (shapeSize + 13); // 40/3≈13
|
||||
const y = startY;
|
||||
|
||||
// 绘制示例图形
|
||||
drawShape(ctx, shape, x, y, shapeSize, shape.fillColor);
|
||||
|
||||
// // 绘制长方形
|
||||
// ctx.fillStyle = '#fff';
|
||||
// ctx.strokeStyle = '#000';
|
||||
// ctx.lineWidth = 4;
|
||||
const rectangleX = x - rectWidth / 2;
|
||||
const rectangleY = y + shapeSize / 2 + 10;
|
||||
// ctx.strokeRect(rectangleX, rectangleY, rectWidth, rectHeight);
|
||||
const rectangleY = y + shapeSize / 2 + 3; // 10/3≈3
|
||||
|
||||
// 绘制图形名称
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = 'bold 36px "Microsoft Yahei"';
|
||||
ctx.font = 'bold 12px "Microsoft Yahei"'; // 36/3=12
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(
|
||||
@@ -192,33 +76,29 @@ class ShapeDrawService {
|
||||
);
|
||||
});
|
||||
|
||||
this.currentY = this.headerType === 'minimal' ? 532 : 622;
|
||||
this.drawLine(this.currentY);
|
||||
this.currentY = this.headerType === 'minimal' ? 180 : 220; // 532/3≈177, 622/3≈207
|
||||
this.drawDivider();
|
||||
}
|
||||
|
||||
drawContent() {
|
||||
const { canvas, ctx, shapes } = this;
|
||||
const { ctx, shapes } = this;
|
||||
if (shapes.length <= 0) return;
|
||||
|
||||
// 固定可渲染的总行数
|
||||
// 逻辑像素尺寸(原始尺寸除以3)
|
||||
const ROW_COUNT = 6;
|
||||
// 每行之间的垂直间距
|
||||
const VERTICAL_GAP = 120;
|
||||
// 图形大小
|
||||
const shapeSize = 180;
|
||||
// 左右边距
|
||||
const leftMargin = 160;
|
||||
const rightMargin = 160;
|
||||
// 以 drawLegend 画完后的分割线作为基准,内容区顶部间距 50
|
||||
const VERTICAL_GAP = 40; // 120/3=40
|
||||
const shapeSize = 60; // 180/3=60
|
||||
const leftMargin = 53; // 160/3≈53
|
||||
const rightMargin = 53; // 160/3≈53
|
||||
const legendBottomY = this.currentY;
|
||||
const topGap = 60;
|
||||
const topGap = 20; // 60/3=20
|
||||
const contentTop = legendBottomY + topGap;
|
||||
|
||||
// 可用宽度
|
||||
const contentWidth = canvas.width - leftMargin - rightMargin;
|
||||
const contentWidth = this.canvasWidth - leftMargin - rightMargin;
|
||||
|
||||
// 每行最多能放多少个图形(考虑最小间距40)
|
||||
const minGap = 40;
|
||||
// 每行最多能放多少个图形(考虑最小间距,逻辑像素)
|
||||
const minGap = 13; // 40/3≈13
|
||||
const maxPerRow = Math.floor(
|
||||
(contentWidth + minGap) / (shapeSize + minGap),
|
||||
);
|
||||
@@ -300,35 +180,9 @@ class ShapeDrawService {
|
||||
});
|
||||
}
|
||||
|
||||
// drawLine 已由基类的 drawDivider 替代,保留此方法以保持兼容
|
||||
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();
|
||||
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);
|
||||
this.drawDivider();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
// import { PAPER_SIZE } from './constant';
|
||||
import { PAPER_SIZE } from '../constants/colors';
|
||||
import {
|
||||
drawHeader as drawHeaderCommon,
|
||||
drawMiniHeader as drawMiniHeaderCommon,
|
||||
} from './headerDrawService';
|
||||
|
||||
import { BaseDrawService } from './baseDraw';
|
||||
/**
|
||||
* 计算示例区域圆的中心点
|
||||
* @param canvasWidth 画布宽度
|
||||
* @param canvasWidth 画布宽度(逻辑像素)
|
||||
* @param circleCount 圆的数量
|
||||
* @param padding 圆的间距
|
||||
* @param radius 圆的半径
|
||||
* @param padding 圆的间距(逻辑像素)
|
||||
* @param radius 圆的半径(逻辑像素)
|
||||
* @returns 圆的中心点
|
||||
*/
|
||||
function calculateCircleCenters(
|
||||
@@ -24,7 +18,6 @@ function calculateCircleCenters(
|
||||
const centers = [];
|
||||
|
||||
if (circleCount === 1) {
|
||||
// 单个圆圈直接居中
|
||||
centers.push(canvasWidth / 2);
|
||||
} else {
|
||||
const requiredSpace = 2 * radius * circleCount;
|
||||
@@ -32,7 +25,6 @@ function calculateCircleCenters(
|
||||
(availableWidth - requiredSpace) / (circleCount - 1);
|
||||
|
||||
if (spaceBetween > maxSpacing) {
|
||||
// 超过最大间距时,固定间距并居中对齐
|
||||
const totalWidth = 2 * radius + (circleCount - 1) * maxSpacing;
|
||||
const startX = (canvasWidth - totalWidth) / 2 + radius;
|
||||
|
||||
@@ -40,7 +32,6 @@ function calculateCircleCenters(
|
||||
centers.push(startX + i * maxSpacing);
|
||||
}
|
||||
} else {
|
||||
// 正常均匀分布
|
||||
const startX = padding + radius;
|
||||
|
||||
for (let i = 0; i < circleCount; i++) {
|
||||
@@ -52,14 +43,7 @@ function calculateCircleCenters(
|
||||
return centers;
|
||||
}
|
||||
|
||||
class TextDrawService {
|
||||
headerType: PrintHeader = 'wechat';
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
class TextDrawService extends BaseDrawService {
|
||||
colors: string[];
|
||||
characters: string[];
|
||||
|
||||
@@ -69,27 +53,13 @@ class TextDrawService {
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
options = options || {};
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦丫小程序',
|
||||
appHint: '识字|识图|练字|打印',
|
||||
super(canvas, ctx, {
|
||||
title: '找一找 涂 色',
|
||||
subTitle: '给文字涂上相同的颜色',
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
});
|
||||
this.colors = ['#000'];
|
||||
this.characters = ['王'];
|
||||
this.setPrintConfig();
|
||||
}
|
||||
|
||||
setPrintConfig() {
|
||||
const printConfig = getApp().getPrintConfig();
|
||||
this.headerType = printConfig.header;
|
||||
this.options.appName = printConfig.appName;
|
||||
}
|
||||
|
||||
async draw(list: Array<{ color: string; word: string }>) {
|
||||
@@ -99,82 +69,58 @@ class TextDrawService {
|
||||
this.characters = list.map((item) => item.word || '日');
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 绘制Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
this.drawMiniHeader();
|
||||
await this.drawMiniHeader();
|
||||
}
|
||||
|
||||
this.drawDivider();
|
||||
this.drawLegend();
|
||||
this.drawContent();
|
||||
}
|
||||
|
||||
async drawHeader() {
|
||||
this.currentX = 80;
|
||||
this.currentY = 80;
|
||||
await drawHeaderCommon({
|
||||
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;
|
||||
this.drawLine(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
drawMiniHeader() {
|
||||
drawMiniHeaderCommon({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
options: {
|
||||
appName: this.options.appName || '涂鸦丫小程序',
|
||||
title: this.options.title || '找一找 涂 色',
|
||||
},
|
||||
onHeaderDrawn: (currentY) => {
|
||||
this.currentY = currentY;
|
||||
this.drawLine(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 绘制示例
|
||||
* 矩形: x、y为左上角
|
||||
* 圆形:x、y为圆心
|
||||
* 文字:x textAlign 为 start,文本左边缘对齐
|
||||
* y textBaseline 为 alphabetic,y 对应字母基线(类似左下角)
|
||||
*
|
||||
* 注意:所有尺寸已转换为逻辑像素(除以3)
|
||||
* */
|
||||
drawLegend() {
|
||||
const { canvas, ctx, colors, characters } = this;
|
||||
const { ctx, colors, characters } = this;
|
||||
if (characters.length <= 0) return;
|
||||
// const { colors, characters } = options;
|
||||
this.currentY = this.headerType === 'minimal' ? 200 : 304;
|
||||
const radius = 65;
|
||||
const rectWidth = 180;
|
||||
const rectHeight = 80;
|
||||
const startX = 260 + radius; // 圆形的X坐标是圆心
|
||||
const startY = this.currentY + 30 + radius; // 圆形的Y坐标是圆心
|
||||
|
||||
// 逻辑像素尺寸(原始尺寸除以3)
|
||||
this.currentY = this.headerType === 'minimal' ? 68 : 110; // 200/3≈67, 304/3≈101
|
||||
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 = this.currentY + 10 + radius; // (30+65)/3≈32
|
||||
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;
|
||||
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; // 计算圆的X坐标
|
||||
const y = startY; // 计算圆的Y坐标
|
||||
const x = centers[index] || startX + index * spaceWidth;
|
||||
const y = startY;
|
||||
// 绘制圆
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = 4;
|
||||
ctx.lineWidth = 1; // 4/3≈1
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
@@ -182,16 +128,16 @@ class TextDrawService {
|
||||
ctx.closePath();
|
||||
|
||||
// 绘制长方形
|
||||
ctx.fillStyle = '#fff'; // 长方形背景色
|
||||
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.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 48px "Microsoft Yahei"';
|
||||
ctx.font = 'bold 16px "Microsoft Yahei"'; // 48/3=16
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(
|
||||
@@ -202,26 +148,25 @@ class TextDrawService {
|
||||
);
|
||||
});
|
||||
|
||||
this.currentY = this.headerType === 'minimal' ? 522 : 612; // 计算分割线的Y坐标,距离示例图20px
|
||||
this.drawLine(this.currentY);
|
||||
this.currentY = this.headerType === 'minimal' ? 178 : 230; // 522/3=174, 612/3=204
|
||||
this.drawDivider();
|
||||
}
|
||||
|
||||
drawContent() {
|
||||
const { canvas, ctx, characters } = this;
|
||||
const { ctx, characters } = this;
|
||||
if (characters.length <= 0) return;
|
||||
|
||||
const len = characters.length;
|
||||
const rows = this.headerType === 'minimal' ? 9 : 8;
|
||||
const radius = 80;
|
||||
const fontSize = 72;
|
||||
const radius = 27; // 80/3≈27
|
||||
const fontSize = 24; // 72/3=24
|
||||
|
||||
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;
|
||||
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++) {
|
||||
@@ -238,7 +183,7 @@ class TextDrawService {
|
||||
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.lineWidth = 1; // 4/3≈1
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
@@ -254,36 +199,9 @@ class TextDrawService {
|
||||
}
|
||||
}
|
||||
|
||||
// drawLine 已由基类的 drawDivider 替代,保留此方法以保持兼容
|
||||
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);
|
||||
this.drawDivider();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { PAPER_SIZE } from '../constants/colors';
|
||||
import { BaseDrawService } from './baseDraw';
|
||||
import { CharacterItem } from '../types/characterType';
|
||||
import {
|
||||
drawHeader as drawHeaderCommon,
|
||||
drawMiniHeader as drawMiniHeaderCommon,
|
||||
} from './headerDrawService';
|
||||
|
||||
/**
|
||||
* 仅支持 M/L/Z 的简单 SVG 路径解析与绘制
|
||||
@@ -197,14 +193,14 @@ function drawTianZiGrid({
|
||||
lineColor = '#e0e0e0',
|
||||
boldColor = '#cccccc',
|
||||
}: DrawTianZiGridParams) {
|
||||
// 外框
|
||||
// 外框(逻辑像素)
|
||||
ctx.strokeStyle = boldColor;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineWidth = 1; // 2/3≈1,逻辑像素
|
||||
ctx.strokeRect(x - size / 2, y - size / 2, size, size);
|
||||
|
||||
// 中线
|
||||
// 中线(逻辑像素)
|
||||
ctx.strokeStyle = lineColor;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineWidth = 1; // 逻辑像素
|
||||
ctx.beginPath();
|
||||
// 竖线
|
||||
ctx.moveTo(x, y - size / 2);
|
||||
@@ -215,7 +211,7 @@ function drawTianZiGrid({
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// 对角线(淡)
|
||||
// 对角线(淡,逻辑像素)
|
||||
ctx.strokeStyle = '#eeeeee';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
@@ -227,40 +223,18 @@ function drawTianZiGrid({
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
class WordDrawService {
|
||||
headerType: PrintHeader = 'wechat';
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
|
||||
class WordDrawService extends BaseDrawService {
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
options = options || {};
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦丫小程序',
|
||||
appHint: '识字|识图|练字|打印',
|
||||
super(canvas, ctx, {
|
||||
title: '田字格 练 字 贴',
|
||||
subTitle: '按笔画临摹练习',
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
this.setPrintConfig();
|
||||
}
|
||||
|
||||
setPrintConfig() {
|
||||
const printConfig = getApp().getPrintConfig();
|
||||
this.headerType = printConfig.header;
|
||||
this.options.appName = printConfig.appName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,12 +245,14 @@ class WordDrawService {
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 等待页眉绘制完成,确保 this.currentY 被正确设置
|
||||
// 绘制Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
await this.drawMiniHeader();
|
||||
this.drawMiniHeader();
|
||||
}
|
||||
|
||||
this.drawDivider();
|
||||
this.drawContentEmpty();
|
||||
}
|
||||
|
||||
@@ -301,60 +277,19 @@ class WordDrawService {
|
||||
* 清空内容区域(页眉以下的部分)
|
||||
*/
|
||||
private clearContentArea() {
|
||||
const { ctx, canvas } = this;
|
||||
const { ctx } = this;
|
||||
const contentStartY = this.currentY;
|
||||
|
||||
// 清空页眉以下的所有内容
|
||||
// 清空页眉以下的所有内容(使用逻辑像素)
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillRect(
|
||||
0,
|
||||
contentStartY,
|
||||
canvas.width,
|
||||
canvas.height - contentStartY,
|
||||
this.canvasWidth,
|
||||
this.canvasHeight - contentStartY,
|
||||
);
|
||||
}
|
||||
|
||||
async drawHeader() {
|
||||
this.currentX = 80;
|
||||
this.currentY = 80;
|
||||
await drawHeaderCommon({
|
||||
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: () => {
|
||||
// wordDrawService 使用不同的计算方式
|
||||
const titleY = 80;
|
||||
const headerHeight = Math.max(titleY + 64 + 48 + 48) + 0; // 64px字体 + 48px间距 + 48px字体 + 20px边距
|
||||
this.currentY = 80 + headerHeight;
|
||||
this.drawDivider(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
drawMiniHeader() {
|
||||
drawMiniHeaderCommon({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
options: {
|
||||
appName: this.options.appName || '涂鸦丫小程序',
|
||||
title: this.options.title || '田字格 练 字 贴',
|
||||
},
|
||||
onHeaderDrawn: () => {
|
||||
// wordDrawService 使用不同的计算方式
|
||||
const titleY = 120;
|
||||
const miniHeaderHeight = titleY + 64 + 20; // 64px字体 + 20px边距
|
||||
this.currentY = miniHeaderHeight;
|
||||
this.drawDivider(this.currentY);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制正文内容:两阶段绘制 - 先绘制空田字格,再绘制练字内容
|
||||
*/
|
||||
@@ -368,18 +303,18 @@ class WordDrawService {
|
||||
* rowGap(行间距)用于田字格竖直方向(每一行田字格之间的间隔)的初始最小值。它防止行与行的田字格上下贴得太紧,增加一定的可视呼吸空间。之后同样会结合剩余空间动态调整为实际间距 actualRowGap。
|
||||
*/
|
||||
drawContent(characterData: CharacterItem[] | null) {
|
||||
const { canvas, ctx } = this;
|
||||
const { ctx } = this;
|
||||
|
||||
// 布局参数
|
||||
const topGap = 50; // 与页眉分割线的距离
|
||||
const leftMargin = 120;
|
||||
const rightMargin = 120;
|
||||
const bottomMargin = 120;
|
||||
// 布局参数(逻辑像素,原始尺寸除以3)
|
||||
const topGap = 17; // 50/3≈17
|
||||
const leftMargin = 40; // 120/3=40
|
||||
const rightMargin = 40; // 120/3=40
|
||||
const bottomMargin = 40; // 120/3=40
|
||||
const contentTop = this.currentY + topGap;
|
||||
const contentWidth = canvas.width - leftMargin - rightMargin;
|
||||
const contentHeight = canvas.height - contentTop - bottomMargin;
|
||||
const contentWidth = this.canvasWidth - leftMargin - rightMargin;
|
||||
const contentHeight = this.canvasHeight - contentTop - bottomMargin;
|
||||
|
||||
const cellSize = 140;
|
||||
const cellSize = 47; // 140/3≈47
|
||||
// 统一通过 getMaxGridLayout 获取最大行列数
|
||||
const { maxRow, maxCol } = this.getMaxGridLayout();
|
||||
|
||||
@@ -423,20 +358,18 @@ class WordDrawService {
|
||||
* 获取当前页面可绘制田字格的最大行数与列数(与绘制使用同一套计算规则)
|
||||
*/
|
||||
getMaxGridLayout(): { maxRow: number; maxCol: number } {
|
||||
const { canvas } = this;
|
||||
|
||||
// 布局参数需与 drawContent 保持一致
|
||||
const topGap = 50;
|
||||
const leftMargin = 120;
|
||||
const rightMargin = 120;
|
||||
const bottomMargin = 120;
|
||||
// 布局参数需与 drawContent 保持一致(逻辑像素)
|
||||
const topGap = 17; // 50/3≈17
|
||||
const leftMargin = 40; // 120/3=40
|
||||
const rightMargin = 40; // 120/3=40
|
||||
const bottomMargin = 40; // 120/3=40
|
||||
const contentTop = this.currentY + topGap;
|
||||
const contentWidth = canvas.width - leftMargin - rightMargin;
|
||||
const contentHeight = canvas.height - contentTop - bottomMargin;
|
||||
const contentWidth = this.canvasWidth - leftMargin - rightMargin;
|
||||
const contentHeight = this.canvasHeight - contentTop - bottomMargin;
|
||||
|
||||
const cellSize = 140;
|
||||
const minGap = 24;
|
||||
const rowGap = 36;
|
||||
const cellSize = 47; // 140/3≈47
|
||||
const minGap = 8; // 24/3=8
|
||||
const rowGap = 12; // 36/3=12
|
||||
|
||||
const maxCol = Math.max(
|
||||
1,
|
||||
@@ -596,7 +529,7 @@ class WordDrawService {
|
||||
uptoInclusive: strokes.length - 1,
|
||||
fillStyle: 'rgb(0,0,0)', // 黑色填充
|
||||
strokeStyle: 'rgb(0,0,0)', // 黑色描边
|
||||
lineWidth: 4, // 较粗的线条
|
||||
lineWidth: 1, // 较粗的线条(4/3≈1,逻辑像素)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -631,7 +564,7 @@ class WordDrawService {
|
||||
|
||||
// console.log(`练习格 [${rowIndex}, ${columnIndex}] 坐标: (${x}, ${y}), 笔画: ${strokeIndex + 1}/${strokes.length}`);
|
||||
|
||||
// 绘制到指定笔画的汉字(红色,中等粗细)
|
||||
// 绘制到指定笔画的汉字(灰色,中等粗细)
|
||||
drawStrokes({
|
||||
ctx,
|
||||
strokes,
|
||||
@@ -641,38 +574,13 @@ class WordDrawService {
|
||||
uptoInclusive: strokeIndex,
|
||||
fillStyle: '#ccc',
|
||||
strokeStyle: '#ccc',
|
||||
lineWidth: 3, // 中等粗细
|
||||
lineWidth: 1, // 中等粗细(3/3=1,逻辑像素)
|
||||
});
|
||||
}
|
||||
|
||||
drawDivider(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();
|
||||
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);
|
||||
// drawDivider 已在基类中实现,此方法保留以保持兼容
|
||||
drawDivider(linY?: number) {
|
||||
super.drawDivider();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user