- Canvas-based dual display (image + text) - Grid rendering system with layout support - Uniform font size rendering - Double-line small character handling - Comprehensive documentation of OCR rules and algorithms 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
5.0 KiB
5.0 KiB
古籍OCR渲染的三大关键问题
问题1:逻辑行到物理行的划分(换行问题)
场景
一个逻辑列(line_id)有100个字,但版式只有20行,如何换行填充?
答案
根据y坐标计算每个字符的物理行号
const cellHeight = canvasHeight / rowsPerColumn; // 每行的高度
const row = Math.round(char.yCenter / cellHeight); // 字符中心y坐标 / 行高
关键点
- 不是顺序填充:不能简单地从第0行开始依次填充
- 基于坐标计算:每个字符的行号 = y坐标 ÷ 行高(四舍五入)
- 人工校对保证:人工校对后的数据,每列字符数不会超过版式限制
示例
版式:20行,行高 = 6000px / 20 = 300px
字符A: y=1500 → row = 1500/300 = 5 → 第5行
字符B: y=1800 → row = 1800/300 = 6 → 第6行
字符C: y=2100 → row = 2100/300 = 7 → 第7行
问题2:空列的判断(物理列到网格列的映射)
场景
- line_id=0 对应物理第1列(最右)
- line_id=1 对应物理第8列(最左)
- 中间的2-7列是空的
如何正确映射?
错误做法(当前程序)
// 按物理列顺序映射
物理列0 → 网格列0
物理列1 → 网格列1
问题:忽略了空列,导致位置错误
正确做法1:基于x坐标计算
const cellWidth = canvasWidth / totalColumns; // 每列宽度
const gridCol = Math.round((canvasWidth - xCenter) / cellWidth);
问题:如果页面有左右边距,计算会不准
正确做法2:基于列间距推断(推荐)
步骤:
- 计算所有物理列的x中心:
[2900, 2600, 2300, ...] - 计算相邻物理列的间距:
[300, 300, 300, ...] - 识别异常大的间距(如900px),说明中间有空列
- 根据间距计算空列数:
空列数 = floor(大间距 / 平均间距) - 1
示例:
物理列1: x=2900
物理列2: x=900 间距=2000px
平均间距: 300px
→ 中间空列数 = floor(2000/300) - 1 = 5列
→ 物理列1 → 网格列0
→ 物理列2 → 网格列6(0+1+5空列)
问题3:起始位置和列内空格
3.1 列的起始行(是否顶格)
判断方法:看该列第一个字符的y坐标
const firstChar = column.chars[0]; // 按y排序后的第一个字符
const startRow = Math.round(firstChar.yCenter / cellHeight);
if (startRow === 0) {
console.log("顶格开始");
} else {
console.log(`从第${startRow}行开始,前面有${startRow}个空格`);
}
3.2 同一物理列内逻辑列之间的空格
场景:一个物理列包含多个逻辑列
物理列1:
line_id=0: 3个大字
line_id=1: 2个小字(配成1格)
line_id=2: 5个大字
如何判断 line_id=0 和 line_id=1 之间有没有空格?
方法:按y坐标排序后,检查相邻字符的行号差
// 合并所有字符,按y排序
allChars.sort(by y);
for (let i = 0; i < allChars.length - 1; i++) {
const currentRow = Math.round(allChars[i].yCenter / cellHeight);
const nextRow = Math.round(allChars[i+1].yCenter / cellHeight);
const gap = nextRow - currentRow - 1; // 减1是因为相邻字符行号差1是正常的
if (gap > 0) {
console.log(`第${currentRow}行到第${nextRow}行之间有${gap}个空格`);
}
}
3.3 双行小字内部的处理
重要:同一格内的双行小字(左右两个字),它们的y坐标可能略有不同
处理:
- 按line_id分组(右列一个line_id,左列一个line_id)
- 右列第i个字 配 左列第i个字(都按y排序)
- 配对后,取右字的y坐标作为这一格的位置
// 右列: [字A(y=1500), 字B(y=1800)]
// 左列: [字C(y=1510), 字D(y=1790)]
配对1: [A|C] → y=1500 → row=5
配对2: [B|D] → y=1800 → row=6
完整处理流程
1. 按line_id分组 → 逻辑列
2. 按x坐标聚合 → 物理列
3. 计算列间距 → 识别空列 → 映射到网格列
4. 处理每个物理列:
4.1 合并所有字符(大字+小字配对)
4.2 按y坐标排序
4.3 为每个字符/配对计算行号
4.4 检测空行
4.5 填充到grid[col][row]
0019A.json 的正确结果
根据你的描述:
物理列1(line_ids: 0,1,2,3)
- 小字[左|手] 从第2行开始 →
1180000... - l2和l3中间有1个空格 → 需要检测l2最后一字和l3第一字的行号差
物理列2(line_id: 4)
- 前面1个空格 →
10000...
物理列3(line_id: 5)
- 前面2个空格 →
110000...
物理列4(line_id: 6)和物理列5(line_id: 7)
- 都是顶格 →
0000...
物理列6(line_ids: 8,9)
- 前面1个空格 →
10000... - l8和l9之间1个空格 → 需要检测
物理列7(line_id: 10)
- 前面1个空格 →
10000...
待实现的算法
- y坐标 → 行号 的准确计算
- x坐标 → 网格列 的映射(含空列检测)
- 空行检测 逻辑
- 双行小字 按line_id配对(不是按x分左右)