列宽补偿用了乘法,连跑六次表格就撑爆了
问题
我在做一个把 Excel 导出成 PDF 去打印的功能。A4 纸横向、固定页宽,表格列宽加起来正好铺满一整页。但有些表格内容多了,放不进一页,我就加了个”压高度”的功能——内容太高就把整张表按比例缩小,这样总能塞进一页。
一开始挺好使,打印都正常。
现象
上线过了几天,用户打印某个表格时,右边两列莫名其妙跑到了第二页,表被横着切成两半。这个表格我之前明明测过是好的。
而且这个 bug 挺好玩:不是所有表格都出问题,只有那些内容多、需要缩放的表格才出问题;不缩放的表格压根不走我那段代码。所以测的时候一直没撞上。
怎么解决
查下来发现,问题出在我”压高度”的那个实现思路上。
我为了让宽度看起来不变,做法是这样的:整表缩放会把列宽也缩小,所以我就乘一个系数把列宽撑回去,渲染时又被缩放缩回来,视觉上宽度不变,只有高度被压下去了。听着挺聪明。
但这个”乘系数”的动作重复跑结果不一样——同一个函数被调用两次,宽度就乘两次。
而我那个设置打印的函数,在一次生成流程里被上游调用了六次。1.05 的系数乘六次,原本 11.87 英寸的表格就被撑到了 15.26 英寸,比可打印的 11.29 英寸宽出一大截。右边两列放不下,就被挤到下一页去了。
修法是换思路:不再”在现有宽度上乘”,改成”算绝对目标宽度”。不管调用多少次,每次都直接把列宽调整到”目标宽度 × 缩放比例”,让它稳定在同一个值,不依赖之前被调成了什么样。为了精确到所有列,我用二分法找一个统一的缩放因子,让所有列宽加起来正好等于目标。
这样就治好了:连着跑六次,总宽稳定在 11.86 英寸,渲染出来 11.27 英寸,正好塞进可打印范围,横向纵向都不溢出。
另外还顺手修了另一个老毛病:纵向老是差一点放不下。我原来留了固定的 6pt 余量,但表格渲染时每行高度会取整到像素,18 行下来能累积出 9~10 点的误差,固定 6 点根本不够。改成余量随行数放大——行数越多留得越多,这样才真正稳。
结论
任何”乘系数""加偏移”式的修补,都要先问一句:这个函数重复跑几次,结果还一样吗?
只要一个函数可能被调用多次,而它的行为是”在别人已有的结果上再乘一次/再加一次”,就一定会出事。这次是六次,不多,但乘起来足够把 11 英寸撑到 15 英寸。
写这类补丁(缩放、补偿、归一化)的时候,更稳的做法是”算绝对目标值”,而不是”在别人算过的结果上再加工”。绝对目标天然不怕重复调用,不用额外操心。
反过来,判断自己有没有踩这个坑,有一个笨办法很有效:把那段代码单独拎出来,连续跑两遍,看结果变没变。