511 lines
14 KiB
Markdown
511 lines
14 KiB
Markdown
|
|
# 古籍OCR数据规则文档
|
|||
|
|
|
|||
|
|
## 一、基本概念
|
|||
|
|
|
|||
|
|
### 1.1 古籍版式
|
|||
|
|
- 古籍采用**固定版式**,每页 M列 × N行(如10列×25行)
|
|||
|
|
- 阅读顺序:**从右往左,从上往下**
|
|||
|
|
- 每列内文字**连续排列,无空格**(同列内不会出现空行)
|
|||
|
|
|
|||
|
|
### 1.2 物理列 vs 逻辑列
|
|||
|
|
- **物理列**:页面上实际的一列位置(如"第3列")
|
|||
|
|
- **逻辑列**:JSON中 `line_id` 标识的一组字符
|
|||
|
|
|
|||
|
|
**重要约束**:
|
|||
|
|
1. 一个物理列可能包含多个逻辑列(line_id),尤其是包含双行小字时
|
|||
|
|
2. ⭐ **一个逻辑列永远不会跨越两个物理列** - 逻辑列完整地属于一个物理列
|
|||
|
|
3. 一个物理列内的多个逻辑列按y坐标排列,可能之间有空行
|
|||
|
|
|
|||
|
|
### 1.3 大字与小字
|
|||
|
|
- **大字**:正文字符,`charMarking = []`(空数组)
|
|||
|
|
- **小字**:双行注释/夹注,`charMarking = [0]`(非空数组)
|
|||
|
|
- 判断方式:**只需判断空/非空**,不会有其他复杂情况
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、双行小字规则
|
|||
|
|
|
|||
|
|
### 2.1 基本概念
|
|||
|
|
双行小字是古籍中的夹注,特点:
|
|||
|
|
- **字高与大字相同**(不是缩小的字)
|
|||
|
|
- 一个大字格子**竖向从中间分成两半**
|
|||
|
|
- 右半格一个字,左半格一个字
|
|||
|
|
- 两个小字**共占一格**
|
|||
|
|
|
|||
|
|
### 2.2 在JSON中的表示
|
|||
|
|
双行小字在JSON中是**两个独立的逻辑列**:
|
|||
|
|
- **右列**:先出现的逻辑列(line_id较小)
|
|||
|
|
- **左列**:后出现的逻辑列(line_id较大)
|
|||
|
|
|
|||
|
|
示例(0011B.json):
|
|||
|
|
```
|
|||
|
|
line_id=1: "舊艸堂" (大字,3个)
|
|||
|
|
line_id=2: "元錢惟善..." (小字右列,14个)
|
|||
|
|
line_id=3: "自號曲江..." (小字左列,13个)
|
|||
|
|
line_id=4: "裏橫河橋..." (大字,25个)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 配对规则
|
|||
|
|
- 右列第1字 配 左列第1字 → 第1格
|
|||
|
|
- 右列第2字 配 左列第2字 → 第2格
|
|||
|
|
- ...
|
|||
|
|
- 如果右列多出来,多出的字配空格(空格在左边)
|
|||
|
|
- **不会出现左列比右列多的情况**(º符号表示零次方,0在上/右边)
|
|||
|
|
|
|||
|
|
### 2.4 物理列内的结构表示
|
|||
|
|
用数字表示一个物理列的内容:
|
|||
|
|
- `0` = 大字(占1格)
|
|||
|
|
- `1` = 空格(占1格)
|
|||
|
|
- `8` = 两个小字(占1格,因为8有两个0)
|
|||
|
|
- `º` = 单个落单小字(占1格的半边)
|
|||
|
|
|
|||
|
|
示例:
|
|||
|
|
- `00000000` - 8个大字
|
|||
|
|
- `00088000` - 3大字 + 2格双行小字(4个小字) + 3大字
|
|||
|
|
- `000888º0` - 3大字 + 3格双行小字(7个小字,右4左3) + 1大字
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、JSON数据结构
|
|||
|
|
|
|||
|
|
### 3.1 关键字段
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"FileName": "0011B",
|
|||
|
|
"Width": 3120, // 图片宽度(像素)
|
|||
|
|
"Height": 6004, // 图片高度(像素)
|
|||
|
|
"CharNumber": 186, // 总字符数
|
|||
|
|
"LineNumber": 14, // 逻辑列数(line_id数量)
|
|||
|
|
|
|||
|
|
"chars": ["聞", "賦", ...], // 字符数组
|
|||
|
|
"coors": [[x1,y1,x2,y2], ...], // 坐标数组
|
|||
|
|
"charMarking": [[], [], [0], ...], // 大小字标记
|
|||
|
|
"line_ids": [0, 0, 0, 1, 1, ...], // 逻辑列ID
|
|||
|
|
"char_probs": [0.99, 0.98, ...], // 识别置信度
|
|||
|
|
|
|||
|
|
"text": "完整文本..." // 带换行的文本
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 字段说明
|
|||
|
|
|
|||
|
|
#### charMarking
|
|||
|
|
- `[]` = 大字
|
|||
|
|
- `[0]` = 小字
|
|||
|
|
- **只有这两种情况**
|
|||
|
|
|
|||
|
|
#### line_ids
|
|||
|
|
- 表示逻辑列的**顺序编号**
|
|||
|
|
- **不代表物理位置**(不是"第几列"的意思)
|
|||
|
|
- 相同line_id的字符属于同一逻辑列
|
|||
|
|
|
|||
|
|
#### coors
|
|||
|
|
- 格式:`[x1, y1, x2, y2]`
|
|||
|
|
- 左上角 (x1, y1),右下角 (x2, y2)
|
|||
|
|
- 坐标是**人工校对后的准确值**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、物理列聚合规则
|
|||
|
|
|
|||
|
|
### 4.1 聚合依据
|
|||
|
|
根据逻辑列的**x坐标间隔**判断是否属于同一物理列:
|
|||
|
|
- 间隔小(如<150px)→ 同一物理列
|
|||
|
|
- 间隔大(如>200px)→ 不同物理列
|
|||
|
|
|
|||
|
|
### 4.2 聚合后的结构
|
|||
|
|
一个物理列 = 若干个逻辑列的组合
|
|||
|
|
|
|||
|
|
可能的组合:
|
|||
|
|
1. 单个大字逻辑列
|
|||
|
|
2. 多个大字逻辑列(中间有空行)
|
|||
|
|
3. 大字逻辑列 + 小字右列 + 小字左列 + 大字逻辑列
|
|||
|
|
4. 其他组合...
|
|||
|
|
|
|||
|
|
### 4.3 逻辑列类型判断
|
|||
|
|
- 全是大字(charMarking全为空)→ 大字逻辑列
|
|||
|
|
- 全是小字(charMarking全非空)→ 小字逻辑列(需要和相邻小字列配对)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、网格填充的三大关键问题
|
|||
|
|
|
|||
|
|
### 5.1 问题1:逻辑列到物理行的映射
|
|||
|
|
|
|||
|
|
#### 场景
|
|||
|
|
版式是8列×20行,有11个逻辑列(line_id 0-10),总共103个字。
|
|||
|
|
如何将这些逻辑列正确映射到20行的网格中?
|
|||
|
|
|
|||
|
|
#### 关键约束
|
|||
|
|
- ⭐ **一个逻辑列永远不会跨越两个物理列**
|
|||
|
|
- 人工校对保证了每列字符数不会超过版式限制
|
|||
|
|
|
|||
|
|
#### 正确方法:基于y坐标计算行号
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const cellHeight = canvasHeight / rowsPerColumn; // 每行的高度
|
|||
|
|
const row = Math.round(char.yCenter / cellHeight); // 字符的行号
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤
|
|||
|
|
1. **聚合物理列**:按x坐标将逻辑列聚合到物理列
|
|||
|
|
- 例如:物理列1 = [line_id=0, line_id=1, line_id=2, line_id=3]
|
|||
|
|
|
|||
|
|
2. **合并字符**:在物理列内,合并所有逻辑列的字符
|
|||
|
|
|
|||
|
|
3. **处理双行小字**:识别并配对双行小字(详见2.2节)
|
|||
|
|
|
|||
|
|
4. **计算行号**:为每个字符/配对计算行号
|
|||
|
|
```javascript
|
|||
|
|
for (item of items) {
|
|||
|
|
const row = Math.round(item.yCenter / cellHeight);
|
|||
|
|
grid[col][row] = item;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
5. **检测空行**:相邻字符行号差 > 1 说明中间有空行
|
|||
|
|
|
|||
|
|
#### 示例
|
|||
|
|
```
|
|||
|
|
版式:20行,行高 = 6000px / 20 = 300px
|
|||
|
|
页面高度:6000px
|
|||
|
|
|
|||
|
|
字符A: y=1500 → row = Math.round(1500/300) = 5 → 第5行
|
|||
|
|
字符B: y=1800 → row = Math.round(1800/300) = 6 → 第6行
|
|||
|
|
字符C: y=2400 → row = Math.round(2400/300) = 8 → 第8行(第7行空)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5.2 问题2:物理列到网格列的映射(空列识别)
|
|||
|
|
|
|||
|
|
#### 场景
|
|||
|
|
- line_id=0 对应物理第1列(最右)
|
|||
|
|
- line_id=1 对应物理第8列(最左)
|
|||
|
|
- 中间的2-7列是空的
|
|||
|
|
|
|||
|
|
如何正确映射到8列网格?
|
|||
|
|
|
|||
|
|
#### 错误做法
|
|||
|
|
```javascript
|
|||
|
|
// 按物理列顺序简单映射
|
|||
|
|
物理列0 → 网格列0
|
|||
|
|
物理列1 → 网格列1
|
|||
|
|
```
|
|||
|
|
**问题**:忽略了空列,导致位置完全错误
|
|||
|
|
|
|||
|
|
#### 正确方法:基于列间距识别空列
|
|||
|
|
|
|||
|
|
**步骤**:
|
|||
|
|
|
|||
|
|
1. **计算所有物理列的x中心**
|
|||
|
|
```javascript
|
|||
|
|
physicalColumns = [
|
|||
|
|
{ xCenter: 2900, ... },
|
|||
|
|
{ xCenter: 900, ... }
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **计算相邻物理列的间距**
|
|||
|
|
```javascript
|
|||
|
|
gap1 = 2900 - 900 = 2000px
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **计算标准列间距**
|
|||
|
|
```javascript
|
|||
|
|
// 方法1:基于版式
|
|||
|
|
standardGap = canvasWidth / totalColumns = 3200 / 8 = 400px
|
|||
|
|
|
|||
|
|
// 方法2:基于实际数据(更准确)
|
|||
|
|
// 找出所有"正常间距"(不包含空列的间距),取平均值
|
|||
|
|
// 例如:多个列间距为 [300, 320, 310, 2000]
|
|||
|
|
// 过滤掉异常大的 2000,平均 = 310px
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. **识别空列**
|
|||
|
|
```javascript
|
|||
|
|
if (gap > standardGap * 1.5) {
|
|||
|
|
// 这是一个大间距,中间有空列
|
|||
|
|
emptyColumns = Math.round(gap / standardGap) - 1;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
5. **映射到网格列**
|
|||
|
|
```javascript
|
|||
|
|
物理列0: x=2900 → 网格列0
|
|||
|
|
// 间距2000px,约5个列宽,中间有4个空列
|
|||
|
|
物理列1: x=900 → 网格列5 (0 + 1 + 4空列)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 示例(0019A.json)
|
|||
|
|
```
|
|||
|
|
版式:8列 × 20行
|
|||
|
|
物理列分布:
|
|||
|
|
物理列0: x=2900 → 网格列0
|
|||
|
|
物理列1: x=2600 → 网格列1 (间距300px)
|
|||
|
|
物理列2: x=2300 → 网格列2 (间距300px)
|
|||
|
|
物理列3: x=2000 → 网格列3 (间距300px)
|
|||
|
|
物理列4: x=1700 → 网格列4 (间距300px)
|
|||
|
|
物理列5: x=1400 → 网格列5 (间距300px)
|
|||
|
|
物理列6: x=1100 → 网格列6 (间距300px)
|
|||
|
|
物理列7: x=900 → 网格列7 (间距200px)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5.3 问题3:顶格判断与列内空行
|
|||
|
|
|
|||
|
|
#### 3.1 顶格判断的正确方法
|
|||
|
|
|
|||
|
|
**错误理解**:
|
|||
|
|
```javascript
|
|||
|
|
const row = Math.round(y / cellHeight);
|
|||
|
|
if (row === 0) {
|
|||
|
|
console.log("顶格");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**问题**:
|
|||
|
|
- 页面内容不一定从y=0开始
|
|||
|
|
- 实际内容可能从页面中间开始(y=1500)
|
|||
|
|
- 第一个字y=1500,按公式row=5,但它实际上是该列的第一个字
|
|||
|
|
|
|||
|
|
**⭐ 关键理解**:
|
|||
|
|
> 我们的数据是和真正的古籍一一对应的,一列的第一个字甚至有可能在页面中间继续。顶格不是指y坐标小,而是指在所有列的对齐关系中,该列没有额外的前导空行。
|
|||
|
|
|
|||
|
|
**正确方法:基于多列对齐关系**
|
|||
|
|
|
|||
|
|
1. **找到"参考基准列"**
|
|||
|
|
```javascript
|
|||
|
|
// 找到所有列中,第一个字y坐标最小的那一列
|
|||
|
|
const topMostY = Math.min(...physicalColumns.map(col => col.firstCharY));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **计算每列相对偏移**
|
|||
|
|
```javascript
|
|||
|
|
for (col of physicalColumns) {
|
|||
|
|
const firstCharY = col.chars[0].yCenter; // 该列第一个字
|
|||
|
|
const offsetRows = Math.round((firstCharY - topMostY) / cellHeight);
|
|||
|
|
|
|||
|
|
if (offsetRows === 0) {
|
|||
|
|
console.log(`列${col.index}是顶格`);
|
|||
|
|
} else {
|
|||
|
|
console.log(`列${col.index}前面有${offsetRows}个空行`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **填充网格**
|
|||
|
|
```javascript
|
|||
|
|
for (item of col.items) {
|
|||
|
|
const absoluteRow = Math.round(item.yCenter / cellHeight);
|
|||
|
|
grid[gridCol][absoluteRow] = item;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2 列内空行检测
|
|||
|
|
|
|||
|
|
**场景**:一个物理列包含多个逻辑列
|
|||
|
|
```
|
|||
|
|
物理列1:
|
|||
|
|
line_id=2: 10个大字
|
|||
|
|
line_id=3: 7个大字
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**问题**:line_id=2 和 line_id=3 之间有没有空行?有几个?
|
|||
|
|
|
|||
|
|
**方法**:合并后按y排序,检查相邻字符的行号差
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 1. 合并所有字符
|
|||
|
|
allChars = [...line_id_2的字符, ...line_id_3的字符];
|
|||
|
|
|
|||
|
|
// 2. 按y坐标排序
|
|||
|
|
allChars.sort((a, b) => a.yCenter - b.yCenter);
|
|||
|
|
|
|||
|
|
// 3. 检测空行
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
if (gap > 0) {
|
|||
|
|
console.log(`'${allChars[i].char}'(第${currentRow}行) 到 '${allChars[i+1].char}'(第${nextRow}行) 之间有${gap}个空行`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3 双行小字内部的处理
|
|||
|
|
|
|||
|
|
**重要**:同一格内的双行小字(左右两个字),它们的y坐标可能略有不同
|
|||
|
|
|
|||
|
|
**处理方法**:
|
|||
|
|
1. 识别连续的两个"全小字"逻辑列(按line_id顺序)
|
|||
|
|
2. 第一个逻辑列 = 右列,第二个逻辑列 = 左列
|
|||
|
|
3. 各自按y排序,然后配对:右列第i个 配 左列第i个
|
|||
|
|
4. 配对后,**取右字的y坐标**作为这一格的行号
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 右列line_id=2: [字A(y=1500), 字B(y=1800)]
|
|||
|
|
// 左列line_id=3: [字C(y=1510), 字D(y=1790)]
|
|||
|
|
|
|||
|
|
配对1: [A|C] → y=1500 (取右字) → row=5
|
|||
|
|
配对2: [B|D] → y=1800 (取右字) → row=6
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5.4 完整的网格填充算法
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
算法流程:
|
|||
|
|
|
|||
|
|
1. 按line_id分组 → 得到逻辑列列表
|
|||
|
|
|
|||
|
|
2. 按x坐标聚合 → 得到物理列列表
|
|||
|
|
- 间隔 < 150px:同一物理列
|
|||
|
|
- 间隔 >= 150px:不同物理列
|
|||
|
|
|
|||
|
|
3. 计算列间距 → 识别空列 → 映射到网格列
|
|||
|
|
- 计算标准列间距
|
|||
|
|
- 识别大间距(包含空列)
|
|||
|
|
- 映射:物理列索引 → 网格列索引
|
|||
|
|
|
|||
|
|
4. 处理每个物理列:
|
|||
|
|
4.1 识别并配对双行小字
|
|||
|
|
- 找连续的两个"全小字"逻辑列
|
|||
|
|
- 第一个=右列,第二个=左列
|
|||
|
|
- 按y排序后配对
|
|||
|
|
|
|||
|
|
4.2 合并所有项(大字 + 小字配对)
|
|||
|
|
|
|||
|
|
4.3 按y坐标排序
|
|||
|
|
|
|||
|
|
4.4 为每个项计算行号
|
|||
|
|
row = Math.round(yCenter / cellHeight)
|
|||
|
|
|
|||
|
|
4.5 检测空行
|
|||
|
|
相邻项行号差 > 1
|
|||
|
|
|
|||
|
|
4.6 填充网格
|
|||
|
|
grid[gridCol][row] = item
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5.5 0019A.json 实际案例
|
|||
|
|
|
|||
|
|
版式:**8列 × 20行**
|
|||
|
|
|
|||
|
|
#### 正确的填充结果
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
物理列1 [line_ids: 0,1,2,3] → 网格列0:
|
|||
|
|
格式: 11111800000000000011
|
|||
|
|
第0-4行: 空
|
|||
|
|
第5行: 8 (双行小字 [左|手])
|
|||
|
|
第6-17行: 0 (大字)
|
|||
|
|
第18-19行: 空
|
|||
|
|
|
|||
|
|
物理列2 [line_id: 4] → 网格列1:
|
|||
|
|
格式: 11111000000000111111
|
|||
|
|
第0-4行: 空
|
|||
|
|
第5-12行: 0 (大字13个)
|
|||
|
|
第13-19行: 空
|
|||
|
|
|
|||
|
|
物理列3 [line_id: 5] → 网格列2:
|
|||
|
|
格式: 11111100000001111111
|
|||
|
|
第0-5行: 空
|
|||
|
|
第6-11行: 0 (大字10个)
|
|||
|
|
第12-19行: 空
|
|||
|
|
|
|||
|
|
... (其他列类似)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键点**:
|
|||
|
|
- 所有列第一个字的y坐标都在1400-1650范围
|
|||
|
|
- 通过对齐关系判断:大部分列从第5行开始(前4行空)
|
|||
|
|
- 物理列3从第6行开始(前5行空)
|
|||
|
|
- 列内通过y坐标计算每个字的确切行号
|
|||
|
|
- 相邻字行号差>1表示有空行
|
|||
|
|
|
|||
|
|
## 六、渲染规则
|
|||
|
|
|
|||
|
|
### 6.1 字号
|
|||
|
|
- 大字:标准字号(基于格子高度计算)
|
|||
|
|
- 小字:标准字号 × 0.5(宽度只有一半,字号相应缩小)
|
|||
|
|
- **所有字符使用统一字号**
|
|||
|
|
|
|||
|
|
### 6.2 位置
|
|||
|
|
- 大字:格子中心
|
|||
|
|
- 小字右:格子右半边中心(x + width*0.75)
|
|||
|
|
- 小字左:格子左半边中心(x + width*0.25)
|
|||
|
|
|
|||
|
|
### 6.3 列方向
|
|||
|
|
- 从右往左排列
|
|||
|
|
- 第0列在最右边,第9列在最左边(10列版式)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、数据保证
|
|||
|
|
|
|||
|
|
以下是JSON数据的保证(人工校对后):
|
|||
|
|
|
|||
|
|
1. **字符顺序正确**:按阅读顺序排列
|
|||
|
|
2. **坐标准确**:与物理页面一一对应
|
|||
|
|
3. **line_id正确**:正确分组,不会搞错列
|
|||
|
|
4. **小字配对正确**:右列在前,左列在后
|
|||
|
|
5. **不会出现异常情况**:
|
|||
|
|
- 左列不会比右列多
|
|||
|
|
- charMarking只有空/非空
|
|||
|
|
- 同列内无空格
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、待处理问题
|
|||
|
|
|
|||
|
|
1. **物理列起始位置推断**:如何判断从第几列开始
|
|||
|
|
2. **合并阈值自适应**:不同分辨率可能需要不同阈值
|
|||
|
|
3. **版式参数传入**:如何配置列数和行数
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、示例分析
|
|||
|
|
|
|||
|
|
### 0011B.json 分析
|
|||
|
|
|
|||
|
|
版式:10列 × 25行
|
|||
|
|
|
|||
|
|
逻辑列结构:
|
|||
|
|
```
|
|||
|
|
line_id=0: 大字25个 "聞賦觀濤..."
|
|||
|
|
line_id=1: 大字3个 "舊艸堂"
|
|||
|
|
line_id=2: 小字14个 "元錢惟善..." (右列)
|
|||
|
|
line_id=3: 小字13个 "自號曲江..." (左列)
|
|||
|
|
line_id=4: 大字25个 "裏橫河橋..."
|
|||
|
|
line_id=5: 大字3个 "夜夜明"
|
|||
|
|
line_id=6: 大字25个 "艸船紙馬..."
|
|||
|
|
line_id=7: 大字3个 "鬧黃昏"
|
|||
|
|
line_id=8: 小字10个 "杭俗信鬼..." (右列)
|
|||
|
|
line_id=9: 小字9个 "大街小巷..." (左列)
|
|||
|
|
line_id=10: 大字25个 "松木場前..."
|
|||
|
|
line_id=11: 大字3个 "不倒翁"
|
|||
|
|
line_id=12: 大字25个 "城郭迴環..."
|
|||
|
|
line_id=13: 大字3个 "十景圖"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
物理列聚合(10个物理列):
|
|||
|
|
```
|
|||
|
|
物理列1: [0] - 25大字
|
|||
|
|
物理列2: [1,2,3] - 3大字 + 14格双行小字(27小字)
|
|||
|
|
物理列3: [4] - 25大字
|
|||
|
|
物理列4: [5] - 3大字
|
|||
|
|
物理列5: [6] - 25大字
|
|||
|
|
物理列6: [7,8,9] - 3大字 + 10格双行小字(19小字)
|
|||
|
|
物理列7: [10] - 25大字
|
|||
|
|
物理列8: [11] - 3大字
|
|||
|
|
物理列9: [12] - 25大字
|
|||
|
|
物理列10: [13] - 3大字
|
|||
|
|
```
|