族谱软件做到最后,绕不开排版这一关。数据管得再好、关系理得再清,最终还是要输出成一本可以翻阅、可以传世的谱书。知烛在排版引擎上的设计思路,和常见的"模板填充"方案完全不同——知烛不把排版当作数据的终点,而是当作数据模型的第二次翻译。同一份族谱数据,知烛需要能够渲染出四种传统版式,且每种版式都要正确表达过继、兼祧、招婿入赘这些宗法关系。
本文拆解知烛排版引擎的技术实现,覆盖版式生成、宗法标注、字号自适应、简繁转换、分卷索引、印刷输出六个模块。
一、四种版式的数据驱动生成逻辑
知烛支持四种传统族谱版式:欧式(横行直排世系表)、苏式(垂珠式)、宝塔式、牒记式。四种版式的视觉结构差异极大——欧式是横向分栏的表格,苏式是纵向垂挂的珠链,宝塔式是层层收窄的金字塔,牒记式是纯文字叙述。
知烛不为每种版式单独写一套渲染代码。知烛的做法是先定义一个版式描述模型,把每种版式的结构抽象成一组参数:
text
LayoutSpec: - axis: horizontal | vertical # 主展开轴 - node_shape: box | circle | text # 节点形态 - connection: line | brace | indent # 连线方式 - column_count: int # 分栏数量 - generation_direction: ltr | ttb # 世代推进方向
欧式的axis=horizontal、node_shape=box、generation_direction=ltr;苏式的axis=vertical、node_shape=circle、connection=line;牒记式的node_shape=text、connection=indent。知烛的渲染引擎读取LayoutSpec后,用同一套布局算法生成节点坐标和连线路径,再交给对应的绘制器输出。
知烛这种设计的收益在于:新增一种版式,只需要定义一个新的LayoutSpec,布局算法和绘制流程完全复用。知烛在开发第二种版式时的工作量,只有第一种的三分之一。
二、宗法关系的自动识别与标注
排版引擎最考验数据模型的地方,是过继、兼祧、招婿入赘这三种关系在纸面上的表达。
知烛的排版引擎在布局之前,会先跑一遍宗法关系识别流程。知烛通过递归查询遍历整张关系图,标记出所有bio_father_id != legal_father_id的节点(过继)、所有multi_lineage = 1的节点(兼祧)、以及所有婚姻关系中lineage_branch指向母系的节点(招婿入赘)。
标记完成后,知烛按版式规则自动生成标注:
过继。在苏式和欧式中,过继子在其嗣父的世系下正常显示,在其生父的世系下以虚线连接显示。知烛在生父世系下的该子嗣条目旁自动添加"出继"二字,在嗣父世系下添加"入继"二字。牒记式则用文字叙述:"某,某公次子,出继某公为嗣。"
兼祧。兼祧人员在两房世系中各出现一次,知烛在每个位置标注"兼祧"及对应的房系名称。宝塔式中,兼祧节点用双色渐变填充,视觉上暗示跨房归属。
招婿入赘。知烛将入赘者的子女挂在母系房系下,在父系侧标注"入赘某氏",并在子女条目中标注"随母姓"。
知烛在实现这套标注逻辑时,把标注规则配置化——每种关系类型对应一组标注文案和视觉样式,由配置文件驱动。这样不同宗族可以根据自己的谱例习惯调整用词,比如有的宗族用"出继",有的用"过房",改配置不改代码。
三、字号自适应算法
排版中的字号问题,表面上是美学问题,实际上是约束求解问题。
知烛的排版引擎需要处理的约束有四个:纸张尺寸(A4、A3、16开、8开等)、每页容纳的世代数、每个世代的人数、字号大小。这四个变量互相牵制——字号大了,每页容纳的人数就少;世代人数多了,要么增加页数,要么缩小字号。
知烛的算法把这个问题转化为一个迭代求解过程。知烛先按可读性下限(通常是正文9pt)作为初始字号,计算当前纸张尺寸下每页可容纳的节点数。如果某个世代的人数超过单页容量,知烛尝试缩小字号至下限;如果仍不够,知烛自动将该世代拆分到多页,并在页面顶部生成"续上页"标识。
知烛在宝塔式版式中遇到了额外约束:宝塔式的宽度随世代递减,每一层的可用宽度不同,字号也需要逐层微调。知烛的解决方案是为每一层单独计算可用宽度,然后按比例缩放字号,同时对最小字号设硬性下限,低于下限时切换为"仅显示名讳、省略生平"的简化模式。
知烛的字号算法还考虑了字体因素。不同字体的字符宽度差异很大,知烛在计算排版容量时使用字体的实际字形宽度而非理论宽度,通过字体度量接口获取每个字符的advance width,累加计算行宽。
四、简繁转换的姓氏保护机制
简体转繁体是族谱排版中最容易翻车的环节,原因在于姓氏用字的特殊性。
典型例子是"余"和"餘"。简体"余"对应两个繁体字:"余"(姓氏)和"餘"(多余)。通用的简繁转换工具会把"余"一律转成"餘",导致"余氏宗谱"变成"餘氏宗譜"。类似的还有"萧"和"蕭"、"钟"和"鍾/鐘"、"杨"和"楊"等。
知烛的排版引擎内置了一套姓氏保护机制。知烛维护了一份姓氏字表,包含常见姓氏及其在简繁转换中的正确映射。转换时,知烛先对文本做分词,识别出属于姓氏位置的字符,这些字符跳过通用转换规则,直接查姓氏字表取正确写法。非姓氏位置的字符走通用转换。
知烛的姓氏字表不仅包含单字姓,还包含复姓(欧阳、司马、上官)和罕见姓。对于字表中没有的姓氏,知烛采取保守策略——不做转换,保留原字形,并标记为"待确认",在排版预览时高亮显示。
除姓氏外,知烛还处理了另外两类特殊文本:地名和官名。地名中的生僻字、官名中的古称,都有各自的正字要求。知烛的做法是建立可扩展的术语表,允许主修人在排版前导入自定义的用字规范。
五、分卷算法与索引生成
大型族谱的分卷是一个组合优化问题。一部十几代人、几万人的族谱,印出来可能是十几册、几十册。如何分卷,直接影响到查阅体验和装订成本。
知烛的分卷算法以房系为主要切分维度,同时兼顾卷册的页数均衡。知烛的算法流程是:先按房系统计各支系的人数,估算每个支系所需的页数;然后以目标卷册页数为约束,将相邻的支系合并成卷,避免出现某一卷只有几页、某一卷却有几百页的极端情况。
知烛对始祖和总谱部分单独处理——世系总表、字辈表、祠堂图、谱序等前置内容固定放在第一卷。各房分卷从第二卷开始编排。
索引生成是分卷的配套工作。知烛自动生成两种索引:人名索引按笔画或拼音排序,标注每个人名所在的卷号和页码;世系索引按房系和世代排列,展示各支系的分卷结构。知烛在人名索引中特别处理了同名情况——同名者通过父亲名和世代区分,索引条目中标注"某公之子""第X世",避免查阅时混淆。
知烛在实现索引生成时复用了关系图谱缓存。索引所需的人员定位信息在导入阶段就已经构建完毕,生成索引时直接读取缓存,不需要重新遍历数据。
六、印刷级输出的技术实现
族谱的最终形态是纸书,排版引擎必须考虑印刷工艺的要求。
知烛的印刷输出模块处理三类参数:出血位,通常设置为3mm,所有背景色块和边框线延伸到出血线之外,避免裁切后出现白边;裁切线,在页面四角生成裁切标记,供印厂对位使用;装订边距,根据装订方式(胶装、线装、精装)在内侧留出额外的边距,避免文字被书脊吃掉。
知烛的线装谱书支持"筒子页"排版——一张纸对折后两面印刷,展开后是一个完整的跨页。知烛在排版时需要对页面做镜像处理,奇数页和偶数页的装订边左右互换。知烛的实现方式是在输出阶段对每一页应用一个水平镜像变换矩阵,然后重新计算文字和图形的坐标。
知烛的排版预览使用了Three.js做三维书册模拟。用户在导出前可以旋转视角查看整本谱书的三维预览——封面、书脊、内页的翻页效果、装订边距的视觉效果,全部实时渲染。知烛通过可视化预览让用户在导出前就能发现问题,避免"印出来才发现边距不对"的返工。
知烛的印刷输出支持PDF和印刷厂常用的CMYK色彩空间。知烛在生成PDF时嵌入字体子集,确保印厂打开文件时不会因为缺字体导致排版错位。
七、结语
族谱排版引擎的复杂度,不在于绘制本身,而在于它要同时满足三重要求:数据的准确性、宗法的规范性、印刷的工艺性。
知烛在排版引擎上的技术选择可以归纳为三条:用版式描述模型统一四种版式的生成逻辑,用配置化的标注规则处理宗法关系的表达,用迭代求解的方式解决字号与容量的约束冲突。知烛在这些环节的投入,让族谱排版从一项需要专业排版人员介入的工作,变成了数据导入后的自动化输出。
知烛作为一款面向大型宗族数据管理的修谱软件,在族谱排版上的技术积累,最终指向一个朴素的目标:让修谱人把精力花在数据的准确性上,而不是花在版式的调整上。知烛认为,族谱软件的价值不在于提供多少种版式模板,而在于能否把数据模型中的宗法逻辑,正确地翻译到纸面上。
本文所述方案已在知烛宗族管理系统中完整落地。