Files
hermes-skills/skills/legal/contract-portfolio-analysis/references/onlyoffice-xlsx-rowheight-rendering.md
T

5.1 KiB

OnlyOffice xlsx 行高、合并单元格截断与渲染核查(2026-06-17 万达表确立)

汇总表的「校区整体风险分析」段是横跨整行的合并单元格(如 A11:L11),内容长达 8001200+ 字、3047 个逻辑行。OnlyOffice 渲染这类超长合并单元格时极易截断显示,而 Maggie 对「文字必须完整显示不截断」是硬要求。本文件记录踩过的坑、真实成因、以及可复用的核查方法。

一、行高 409.5/409.6pt 不是格式天花板,是 OnlyOffice 网页编辑器的 clamp 值

这是反复出现的谜题:自己设了 760pt 的行,交付后再打开变成了 409.6pt。 实测厘清三层行为,别再被误导:

行为 实测结论
xlsx 文件格式本身 openpyxl 写 row_dimensions[N].height = 900 → 存盘 → 读回 = 900 文件层不限制行高,900pt 能正常写入并保留
OnlyOffice x2t 批量引擎 xlsx(900pt) 经容器内 x2t 做 xlsx→xlsx round-trip → 读回 row 仍 = 900 不 clamp。从文件层写的高行高,x2t 渲染/转换链路认
OnlyOffice 网页版编辑器 在浏览器里打开编辑、点保存 会把超过 ~409.5pt 的行高压回 409.5/409.6。这就是「我的 760 变成 409.6」的真凶

结论与操作要点:

  • 「调高行高让全部内容显示」在技术上可行——只要从文件层(openpyxl 脚本)写入并经交付链路上传,不要让结果再被网页编辑器存盘一次。
  • 一旦用户/自己在 OnlyOffice 网页端编辑并保存过,超限行高就被打回 409.5。若交付后还要网页端再编辑,高行高保不住。
  • 设了 height 即自动 customHeight=True(openpyxl 中 customHeight 无 setter,不要试图直接赋值,会 AttributeError)。
  • 若 Maggie 明确说「就按当前最高行距,不用调」→ 不折腾,保持现状交付。 别因为自己测出能调高就擅自改——方案≠授权。

二、数据完整 ≠ 视图不截断(核查别只看 pdftotext)

OnlyOffice/x2t 导 PDF 时,被行高裁掉的文字仍会写进 PDF 文本层。所以:

  • pdftotext 提取到尾部锚点句 = 数据在文件里(867 字符全在),但不等于在编辑器视图里可见
  • 真正的「截断」是视图层问题(行高 < 内容所需高度时底部被裁),不是数据丢失。
  • 因此「数据完整性」用 pdftotext 验,「视图是否截断」要靠行高是否 ≥ 内容估算高度来判断(见下方脚本),或渲染成图片肉眼看。
  • ⚠️ 另一个坑:x2t 导 PDF 时对超长合并单元格本身也会截断渲染(PDF 里只画出前一部分,如只渲染「12,000」后面没了)——这是导出视图的固有限制,不代表数据丢。判断数据完整以 openpyxl 读单元格 .value 字符数为准。

三、可复用核查工具

行高分析脚本(再跑用)

scripts/xlsx-rowheight-analyze.py <文件.xlsx>:只读分析每个 sheet 的长内容单元格,按合并宽度 + 字号估算所需视觉行数和建议行高,与当前行高对比,标出「可能截断」的行。不改文件。中文每字≈2.1 宽度单位、每视觉行≈15.5pt(10pt 字)是经验系数。

x2t round-trip 测 clamp / 渲染(本地 OnlyOffice 实例)

容器 nextcloud-onlyoffice-1,x2t 路径 /var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t

# xlsx→xlsx round-trip(测网页引擎是否 clamp 行高;format 257 = xlsx)
docker cp in.xlsx nextcloud-onlyoffice-1:/tmp/t/in.xlsx
docker exec nextcloud-onlyoffice-1 bash -c 'cat > /tmp/t/c.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<TaskQueueDataConvert xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<m_sFileFrom>/tmp/t/in.xlsx</m_sFileFrom><m_sFileTo>/tmp/t/out.xlsx</m_sFileTo>
<m_nFormatTo>257</m_nFormatTo><m_bIsNoBase64>true</m_bIsNoBase64>
</TaskQueueDataConvert>
EOF
LD_LIBRARY_PATH=/var/www/onlyoffice/documentserver/server/FileConverter/bin /var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t /tmp/t/c.xml'
# format 513 = PDF(渲染成 PDF 看版面);之后主机 pdftoppm -png -r 110 out.pdf 转图
  • x2t 退出码 88 = 目标 format code 不对(不是文件坏)。xlsx→xlsx 用 257,xlsx→PDF 用 513;不要走 8193 bin 中间格式做 xlsx round-trip。
  • 主机有 soffice/libreofficepdftoppm/pdfinfo/pdftotext、中文字体 wqy-zenhei,可做降级渲染。

截断检测(文本锚点法)

取目标长单元格尾部几句独特锚点,去掉空格后在 PDF 全文 grep——但记住第二节:命中只证明数据在,视图是否截断仍以行高估算为准。

四、一句话 SOP

  1. 估算:scripts/xlsx-rowheight-analyze.py 文件.xlsx → 看哪些行 当前行高 < 建议行高
  2. 若需调高且用户同意:openpyxl 设 row_dimensions[N].height = 建议值,从文件层写、走交付链路上传,别再用网页端编辑保存
  3. 若用户说保持当前最高 → 不动。
  4. 数据完整性单独用 openpyxl 读 .value 字符数确认,不靠 PDF。