feat:练字贴生成

This commit is contained in:
R524809
2025-10-20 17:40:06 +08:00
parent 2734b9de9e
commit 10b8c12d51
10 changed files with 218 additions and 156 deletions
+85 -93
View File
@@ -116,7 +116,6 @@ function drawStrokes({
}: DrawStrokesParams) {
// 模板中使用 54x54 的视窗尺寸
const viewBoxSize = 54;
const scale = size / viewBoxSize;
// 添加内边距:在田字格四周预留空间,避免笔画贴边
const padding = size * 0.1; // 内边距为田字格大小的10%
@@ -127,22 +126,22 @@ function drawStrokes({
const contentOffsetX = offsetX + padding;
const contentOffsetY = offsetY + padding;
console.log('drawStrokes 参数:', {
strokesCount: strokes.length,
uptoInclusive,
offsetX,
offsetY,
size,
padding,
contentSize,
contentOffsetX,
contentOffsetY,
originalScale: scale,
contentScale,
fillStyle,
strokeStyle,
lineWidth
});
// console.log('drawStrokes 参数:', {
// strokesCount: strokes.length,
// uptoInclusive,
// offsetX,
// offsetY,
// size,
// padding,
// contentSize,
// contentOffsetX,
// contentOffsetY,
// originalScale: scale,
// contentScale,
// fillStyle,
// strokeStyle,
// lineWidth
// });
for (let s = 0; s <= uptoInclusive && s < strokes.length; s++) {
// console.log(`绘制第 ${s} 个笔画:`, strokes[s]);
@@ -357,7 +356,7 @@ class WordDrawService {
drawContent(wordsMap: Record<string, string[]>) {
const { canvas, ctx } = this;
const characters = Object.keys(wordsMap).slice(0, 10);
if (characters.length === 0) return;
// 布局参数
const topGap = 50; // 与页眉分割线的距离
@@ -365,7 +364,6 @@ class WordDrawService {
const rightMargin = 120;
const bottomMargin = 120;
const contentTop = this.currentY + topGap;
console.log('contentTop----:', contentTop)
const contentWidth = canvas.width - leftMargin - rightMargin;
const contentHeight = canvas.height - contentTop - bottomMargin;
@@ -374,24 +372,19 @@ class WordDrawService {
const rowGap = 36; // 最小格子行间距,用于保证纵向不拥挤,后续可动态调整为实际间距
// 计算每行可容纳的田字格数量
const maxPerRow = Math.max(1, Math.floor((contentWidth + minGap) / (cellSize + minGap)));
const columnNumber = Math.max(1, Math.floor((contentWidth + minGap) / (cellSize + minGap)));
// 计算每列可容纳的田字格数量
const actualRows = Math.max(1, Math.floor((contentHeight + rowGap) / (cellSize + rowGap)));
const rowNumber = Math.max(1, Math.floor((contentHeight + rowGap) / (cellSize + rowGap)));
// 计算总需要的田字格数量
const totalCellsNeeded = actualRows * maxPerRow;
// 计算实际需要的行数
console.log('actualRows----:', actualRows)
console.log('totalCellsNeeded----:', totalCellsNeeded)
console.log('maxPerRow----:', maxPerRow)
const totalCellsNeeded = rowNumber * columnNumber;
// 动态调整间距以充分利用空间(在允许的最小间距基础上弹性扩展以适配画布)
const actualRowGap = actualRows > 1 ? (contentHeight - actualRows * cellSize) / (actualRows - 1) : 0;
const actualColGap = maxPerRow > 1 ? (contentWidth - maxPerRow * cellSize) / (maxPerRow - 1) : 0;
const actualRowGap = rowNumber > 1 ? (contentHeight - rowNumber * cellSize) / (rowNumber - 1) : 0;
const actualColGap = columnNumber > 1 ? (contentWidth - columnNumber * cellSize) / (columnNumber - 1) : 0;
console.log(`布局信息: 总格数=${totalCellsNeeded}, 行数=${actualRows}, 列数=${maxPerRow}, 行间距=${actualRowGap.toFixed(1)}, 列间距=${actualColGap.toFixed(1)}`);
console.log(`布局信息: 总格数=${totalCellsNeeded}, 行数=${rowNumber}, 列数=${columnNumber}, 行间距=${actualRowGap.toFixed(1)}, 列间距=${actualColGap.toFixed(1)}`);
// 第一阶段:绘制所有空田字格
this.drawEmptyGrids({
@@ -401,21 +394,23 @@ class WordDrawService {
cellSize,
colGap: actualColGap,
rowGap: actualRowGap,
maxPerRow,
maxRows: actualRows
rowNumber,
columnNumber
});
// 第二阶段:绘制练字内容
this.drawPracticeContent({
ctx,
wordsMap,
startX: leftMargin,
startY: contentTop,
cellSize,
colGap: actualColGap,
rowGap: actualRowGap,
maxPerRow
});
if (characters.length > 0) {
// 第二阶段:绘制练字内容
this.drawPracticeContent({
ctx,
wordsMap,
startX: leftMargin,
startY: contentTop,
cellSize,
colGap: actualColGap,
rowGap: actualRowGap,
columnNumber
});
};
}
/**
@@ -444,8 +439,8 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
maxPerRow,
maxRows
rowNumber,
columnNumber
}: {
ctx: RenderingContext;
startX: number;
@@ -453,12 +448,12 @@ class WordDrawService {
cellSize: number;
colGap: number;
rowGap: number;
maxPerRow: number;
maxRows: number;
rowNumber: number; // 行数
columnNumber: number; // 列数
}) {
console.log('drawEmptyGrids startY----:', startY)
for (let row = 0; row < maxRows; row++) {
for (let col = 0; col < maxPerRow; col++) {
for (let row = 0; row < rowNumber; row++) {
for (let col = 0; col < columnNumber; col++) {
const x = startX + col * (cellSize + colGap) + cellSize / 2;
const y = startY + row * (cellSize + rowGap) + cellSize / 2;
drawTianZiGrid({ ctx, x, y, size: cellSize });
@@ -477,7 +472,7 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
maxPerRow
columnNumber
}: {
ctx: RenderingContext;
wordsMap: Record<string, string[]>;
@@ -486,18 +481,21 @@ class WordDrawService {
cellSize: number;
colGap: number;
rowGap: number;
maxPerRow: number;
columnNumber: number;
}) {
const characters = Object.keys(wordsMap);
let cellIndex = 0;
const reservedCells = 2;
let rowIndex = 0, columnIndex = 0;
console.log('开始绘制练字内容,汉字数量:', characters.length);
// console.log('开始绘制练字内容,汉字数量:', characters.length);
characters.forEach((char, charIndex) => {
const strokes = wordsMap[char] || [];
characters.forEach((uniqueKey, charIndex) => {
const strokes = wordsMap[uniqueKey] || [];
const strokeCount = strokes.length;
// 从uniqueKey中提取原始汉字(去掉后缀)
const originalChar = uniqueKey.split('_')[0];
console.log(`绘制第 ${charIndex + 1} 个汉字 "${char}",笔画数: ${strokeCount}`);
console.log(`绘制第 ${charIndex + 1} 个汉字 "${originalChar}",笔画数: ${strokeCount}`);
// 1. 预览格:显示完整汉字(黑色)
this.drawPreviewCell({
@@ -508,10 +506,9 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
maxPerRow,
cellIndex
rowIndex,
});
cellIndex++;
columnIndex++;
// 2. 练习格:逐笔画显示(红色)
for (let strokeIndex = 0; strokeIndex < strokeCount; strokeIndex++) {
@@ -525,29 +522,30 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
maxPerRow,
cellIndex
rowIndex,
columnIndex
});
cellIndex++;
console.log(`笔画 ${strokeIndex + 1} 绘制完成,当前 cellIndex: ${cellIndex}`);
columnIndex++;
if (columnIndex >= columnNumber) {
columnIndex = 0;
rowIndex++;
}
// console.log(`笔画 ${strokeIndex + 1} 绘制完成,当前 columnIndex: ${columnIndex}`);
}
if (columnIndex + reservedCells > columnNumber) {
columnIndex = 0;
rowIndex++;
}
// 3. 汉字间留空:每个汉字后留2个空白格
cellIndex += 0;
// 4. 强制下一个汉字换行到新行的第一个田字格
if (charIndex < characters.length - 1) { // 不是最后一个汉字
const currentRow = Math.floor(cellIndex / maxPerRow);
const nextRow = currentRow + 1;
cellIndex = nextRow * maxPerRow;
console.log(`汉字 "${char}" 绘制完成,强制换行到新行,下一个汉字从 cellIndex: ${cellIndex} 开始`);
} else {
console.log(`汉字 "${char}" 绘制完成,这是最后一个汉字`);
rowIndex = rowIndex + 1;
columnIndex = 0;
// console.log(`汉字 "${char}" 绘制完成,强制换行到新行,下一个汉字从 rowIndex: ${rowIndex}, columnIndex: ${columnIndex} 开始`);
}
});
console.log('所有汉字绘制完成');
// console.log('所有汉字绘制完成');
}
/**
@@ -561,8 +559,7 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
maxPerRow,
cellIndex
rowIndex
}: {
ctx: RenderingContext;
strokes: string[];
@@ -571,15 +568,13 @@ class WordDrawService {
cellSize: number;
colGap: number;
rowGap: number;
maxPerRow: number;
cellIndex: number;
rowIndex: number;
}) {
const row = Math.floor(cellIndex / maxPerRow);
const col = cellIndex % maxPerRow;
const x = startX + col * (cellSize + colGap);
const y = startY + row * (cellSize + rowGap);
const columnIndex = 0;
const x = startX + columnIndex * (cellSize + colGap);
const y = startY + rowIndex * (cellSize + rowGap);
console.log(`预览格 [${cellIndex}] 坐标: (${x}, ${y}), 行列: (${row}, ${col}), 笔画数: ${strokes.length}`);
// console.log(`预览格 [${rowIndex}, ${columnIndex}] 坐标: (${x}, ${y}), 笔画数: ${strokes.length}`);
// 绘制完整汉字(黑色,较粗)
drawStrokes({
@@ -607,8 +602,8 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
maxPerRow,
cellIndex
rowIndex,
columnIndex
}: {
ctx: RenderingContext;
strokes: string[];
@@ -618,15 +613,13 @@ class WordDrawService {
cellSize: number;
colGap: number;
rowGap: number;
maxPerRow: number;
cellIndex: number;
rowIndex: number;
columnIndex: number;
}) {
const row = Math.floor(cellIndex / maxPerRow);
const col = cellIndex % maxPerRow;
const x = startX + col * (cellSize + colGap);
const y = startY + row * (cellSize + rowGap);
const x = startX + columnIndex * (cellSize + colGap);
const y = startY + rowIndex * (cellSize + rowGap);
console.log(`练习格 [${cellIndex}] 坐标: (${x}, ${y}), 行列: (${row}, ${col}), 笔画: ${strokeIndex + 1}/${strokes.length}`);
// console.log(`练习格 [${rowIndex}, ${columnIndex}] 坐标: (${x}, ${y}), 笔画: ${strokeIndex + 1}/${strokes.length}`);
// 绘制到指定笔画的汉字(红色,中等粗细)
drawStrokes({
@@ -643,7 +636,6 @@ class WordDrawService {
}
drawLine(linY: number) {
console.log('drawLine linY----:', linY)
const { canvas, ctx } = this;
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;