jquick-pdf 表格行列尺寸控制实战:单元格合并的可行边界
引入
表格是报表 PDF 中最容易失控的部分:数值列太窄会折行,说明列过高会让整页行数不足,汇总信息又常被要求"合并居中"。用底层 iText/PDFBox 手写这类表格时,合并单元格意味着自行维护 rowspan/colspan 占位矩阵,列顺序一变,错位往往要等到输出 PDF 才被发现。
jquick-pdf 的处理方式是把几何信息交给模板样式,把结构交给标签。使用前必须先区分"已验证能力"和"未证实能力":本地 sample 已验证 <table>、<tr>、<td>、<th> 以及 width、height 样式可以工作;rowSpan、colSpan 在现有源码与测试中没有被证实,因此本文不提供可运行的合并写法,而是给出可落地的替代布局。环境只说明一次:Maven 引入 io.github.paohaijiao:jquick-pdfx:4.0.0,JDK 8+ 即可运行本文全部代码。
核心讲解
三层尺寸:总宽、行高与列宽
一次表格渲染的几何由三层声明共同决定:
<table style="width:520px">声明表格总宽度;<tr style="height:36px">声明行高,表头行与数据行可以分别设置;<th style="width:160px">与<td>声明列宽。
建议的声明顺序是"先总宽、再分列宽、最后定行高"。列宽之和与总宽不一致时的实际表现(等比缩放、溢出还是忽略其一)属于实现细节,应以本地输出为准,不要凭 HTML 表格的经验推断。单位遵循 px(96 DPI,1px = 0.75pt)、pt、mm、cm、in,因此 520px ≈ 390pt ≈ 137.6mm,在 A4 纵向并保留常规页边距后仍有余量;60px 的数据行高等于 45pt。
固定行高在真实报表中的用途
固定行高解决的是"行的视觉节奏"而不是"内容高度":多行说明文本折行时,只要行高一致,整页基线才统一,扫描式阅读不会跳动。演示中表头行取 36px 并配 #DDEAFE 底色形成层级,数据行取 60px 为两行说明文本预留空间。需要注意,行高一旦固定,内容超出时的处理方式(裁切、撑高还是推到下一页)依实现而定,因此长文本必须靠列宽与文本长度共同约束,而不能只靠行高。
列宽与内容挤压的关系
列宽本质是分配问题:总宽固定时,某一列变宽必然挤压其他列。最容易被挤压的是文本列------宽度不足触发折行,折行又推高该行,最终表现为表格整体变长、分页位置漂移。因此"编号列尽量窄、说明列尽量宽"是常见策略,同时应缩短表头文案("说明"优于"变更说明与当前处理进展"),并在后端对超长字段先做截断或摘要,而不是指望渲染层自动收敛。
用 div 分组替代合并单元格
可行的替代思路有三条:
- 视觉分组:对需要横跨整表的信息,用带
border、padding、backgroundColor的<div>独立成块,放在表格上方或紧随其后,语义上与"跨列单元格"等价; - 空单元格加对齐:同一取值连续多行时留空重复值,配合
textAlignment、verticalAlignment形成视觉连续; - 汇总行整行输出:不合并,把汇总文案写进一列,另一列放数值。
演示采用第一种方式,用 <div style="border:1px solid #999;padding:8px"> 承载"合并展示:项目状态 / 进行中"。
关键细节
- 文本字面量必须用单引号包裹(
'阶段');变量写作${name},已注册资源写作&{name},模板根元素为<pdf>或<html>; - 样式属性名驼峰与连字符互为别名,
backgroundColor与background-color等价; - 尺寸护栏:
minWidth、maxWidth、minHeight、maxHeight可用于异常内容保护,例如给说明列设置maxWidth,但具体生效结果属实现细节,需实测确认; - 边框与圆角:
border与四向边框按「类型 宽度 颜色」书写(如solid 1px #999),borderRadius可用于分组块的视觉区分;颜色支持颜色名、#RRGGBB、rgb()/rgba()与linear-gradient; - 可读性:数字列可用
textAlignment右对齐、表头居中,单元格内文本可用verticalAlignment调整垂直位置; - 长文本影响的是视觉高度:固定
height与长文本叠加时,先看输出 PDF,再决定缩短文本还是拆分表格; - 跨页表现:文档具备自动分页与强制分页能力(
<areaBreak>、<htmlPageBreak>)。固定行高跨页时,页尾剩余空间不足会把整行推到下一页,留下空白带,行高与页面可用高度不匹配会放大这段空白。建议行高取页面可用高度的约数,并把"续页是否重复表头"列入验收项。
实战说明
java
import com.github.paohaijiao.executor.JQuickPdfFactory; // 导入工厂
import java.nio.file.Files; // 导入文件类
import java.nio.file.Paths; // 导入路径类
public class TableSizeDemo { // 声明类
public static void main(String[] args) throws Exception { // 声明入口
String template = "<pdf><body><h1>'项目进度'</h1>" // 创建模板
+ "<table style=\"width:520px\">" // 控制总宽度
+ "<tr style=\"height:36px;backgroundColor:#DDEAFE\">" // 控制表头行高和底色
+ "<th style=\"width:160px\">'阶段'</th><th style=\"width:360px\">'说明'</th></tr>" // 控制列宽
+ "<tr style=\"height:60px\"><td>'需求'</td><td>'已完成评审,进入开发。'</td></tr>" // 控制数据行高
+ "</table><div style=\"border:1px solid #999;padding:8px\">" // 用块模拟分组视觉
+ "<p>'合并展示:项目状态'</p><p>'进行中'</p></div></body></pdf>"; // 结束模板
byte[] pdf = new JQuickPdfFactory().executeContent(template); // 执行渲染
Files.write(Paths.get("table-size.pdf"), pdf); // 保存 PDF
}
}

执行步骤与预期:
- 编译并运行
TableSizeDemo,在当前目录得到table-size.pdf; - 预期标题"项目进度"下出现两行表格:表头行高 36px 且带
#DDEAFE底色,数据行高 60px; - 预期阶段列(160px)明显窄于说明列(360px),正文"已完成评审,进入开发。"不折行;
- 表格下方出现带 1px 灰色边框的
div分组块,内部两段文本分别表示分组标题与当前状态; - 验证方式:用阅读器的测量工具检查实际列宽与行高是否接近声明值;用文本抽取确认单引号内的字面量完整出现;再换一个阅读器打开,确认外观一致;
- 回归检查:把说明文本换成长文本(例如再加三十个字)重跑一次,观察是否折行、表格总高增加了多少,用这个结果反推列宽是否需要调整。
异常排查:
- 行高未生效:检查
style中属性拼写、单位是否带px,以及是否写在了<tr>上; - 列宽被压缩:缩短该列文本或减少列数,必要时调整总宽;
- 渲染报错或文本异常:检查字面量是否漏了单引号,模板根元素是否为
<pdf>或<html>。
生产环境还需注意:本方案适合项目进度、配置清单、审批摘要这类行列结构固定的报表;一旦出现"合计行跨列""行列由数据决定"的需求,应改用后端生成行加视觉分组的方式,而不是尝试未证实的合并属性。
总结
本文结论可以概括为三点。第一,表格几何由总宽、行高、列宽三层共同决定,应按"总宽 → 列宽 → 行高"的顺序声明,并用单位换算(1px = 0.75pt)预估是否适配页面。第二,固定行高服务的是视觉节奏,列宽决定内容是否被挤压,两者都必须与文本长度一起权衡,maxWidth 这类护栏只能作为保护而非排版方案。第三,rowSpan/colSpan 未获证实,涉及合并的需求应使用 div 分组、空单元格对齐或整行汇总来表达。
常见误区有两个:把未知属性写进模板后"不报错"当作"已支持",以及在跨页场景下默认行高会被稳定保持。前者会在验收阶段暴露,后者会在长报表中暴露。
版本基线:jquick-pdfx 4.0.0、JDK 8+,升级前请核对 README_zh.md 的版本对照表,确认表格与样式相关行为是否变化;更多示例见 GitHub 仓库。