# 古籍OCR渲染的三大关键问题 ## 问题1:逻辑行到物理行的划分(换行问题) ### 场景 一个逻辑列(line_id)有100个字,但版式只有20行,如何换行填充? ### 答案 **根据y坐标计算每个字符的物理行号** ```javascript const cellHeight = canvasHeight / rowsPerColumn; // 每行的高度 const row = Math.round(char.yCenter / cellHeight); // 字符中心y坐标 / 行高 ``` ### 关键点 1. **不是顺序填充**:不能简单地从第0行开始依次填充 2. **基于坐标计算**:每个字符的行号 = y坐标 ÷ 行高(四舍五入) 3. **人工校对保证**:人工校对后的数据,每列字符数不会超过版式限制 ### 示例 ``` 版式: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列是空的 如何正确映射? ### 错误做法(当前程序) ```javascript // 按物理列顺序映射 物理列0 → 网格列0 物理列1 → 网格列1 ``` **问题**:忽略了空列,导致位置错误 ### 正确做法1:基于x坐标计算 ```javascript const cellWidth = canvasWidth / totalColumns; // 每列宽度 const gridCol = Math.round((canvasWidth - xCenter) / cellWidth); ``` **问题**:如果页面有左右边距,计算会不准 ### 正确做法2:基于列间距推断(推荐) **步骤**: 1. 计算所有物理列的x中心:`[2900, 2600, 2300, ...]` 2. 计算相邻物理列的间距:`[300, 300, 300, ...]` 3. 识别**异常大的间距**(如900px),说明中间有空列 4. 根据间距计算空列数:`空列数 = 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坐标 ```javascript 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坐标排序后,检查相邻字符的行号差 ```javascript // 合并所有字符,按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坐标可能略有不同 **处理**: 1. 按line_id分组(右列一个line_id,左列一个line_id) 2. 右列第i个字 配 左列第i个字(都按y排序) 3. 配对后,取**右字的y坐标**作为这一格的位置 ```javascript // 右列: [字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...` --- ## 待实现的算法 1. **y坐标 → 行号** 的准确计算 2. **x坐标 → 网格列** 的映射(含空列检测) 3. **空行检测** 逻辑 4. **双行小字** 按line_id配对(不是按x分左右)