基于C#自动调整pdf注释框的方法
众所周知 pdf 注释框是无法随着文字大小而自动调整宽高的,因此在网上搜索了很多文章,但并没有找到好的方法,于是打算使用程序自己写代码进行调整。
准备工作
要想自动调整注释框,首先得知道每个注释框的宽高,以及所属字体,是否加粗等信息。
所幸这些信息在 pdf 导出的注释文件(.xfdf)都有保存,这里不过多展开,以下是 xfdf 截取的部分代码
<freetext rect="177.595444,740.365417,262.641174,786.390137" creationdate="D:20230626143154+08'00'" name="656a7e33-a002-9fc9-5fe2-925bdd59d2e0" opacity="1" flags="print" date="D:20230626143256+08'00'" title="Administrator" subject="文本框" rotation="0" page="0" width="1" head="None" fringe="0,0,0,0">
<contents-richtext><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:7.0.0" xfa:spec="2.0.2" style="font-size:12.00pt;font-family:'Times New Roman'"><p><span style="text-decoration:;font-family:'Arial'">test</span><span style="text-decoration:;font-family:'宋体'">这是一个测试</span><span style="text-decoration:;font-family:'Arial'">
</span></p>
</body>
</contents-richtext>
<defaultappearance>1.000000 0.000000 0.000000 rg /Helv 12 Tf</defaultappearance>
<defaultstyle>text-decoration:;font-size:12.00pt;font-family:'Times New Roman'</defaultstyle></freetext>
freetext 表示文本框,其中 rect 记录了注释框的两个点坐标位置,以及 style 记录了字体的名称,是否加粗等信息。
得到了 xfdf 文件,我们就可以采用软件进行读取写入了(这里有坑,在后续介绍)。
读取XFDF文件
首先需要进行读写文件,xfdf 文件是基于XML格式的文件
//读取xfdf文件
XDocument document = XDocument.Load("blank.xfdf");
这里会遇到一个坑,带有xmlns属性时,无法正常读取文件,网上有提到命名空间的问题,这里图省事,直接使用replace删掉xmlns属性
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<freetext rect="177.595444,740.365417,262.641174,786.390137" creationdate="D:20230626143154+08'00'" name="656a7e33-a002-9fc9-5fe2-925bdd59d2e0" opacity="1" flags="print" date="D:20230626143256+08'00'" title="Administrator" subject="文本框" rotation="0" page="0" width="1" head="None" fringe="0,0,0,0">
<contents-richtext><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:7.0.0" xfa:spec="2.0.2" style="font-size:12.00pt;font-family:'Times New Roman'"><p><span style="text-decoration:;font-family:'Arial'">test</span><span style="text-decoration:;font-family:'宋体'">这是一个测试</span><span style="text-decoration:;font-family:'Arial'">
</span></p>
</body>
</contents-richtext>
<defaultappearance>1.000000 0.000000 0.000000 rg /Helv 12 Tf</defaultappearance>
<defaultstyle>text-decoration:;font-size:12.00pt;font-family:'Times New Roman'</defaultstyle></freetext>
</annots>
<f href=".\blank.pdf"/>
<ids original="8E059E4D73BCD0499DA82455FCF6FC3D" modified="8E059E4D73BCD0499DA82455FCF6FC3D"/>
</xfdf>
即替换掉以下两行代码为空
xmlns="http://ns.adobe.com/xfdf/"
xmlns="http://www.w3.org/1999/xhtml"
计算文字宽高
注意:这里计算文字宽高需要考虑中英文字体分开计算,本文未详细展开
这里采用了第三方库 iTextSharp 来计算字体宽高
//计算文字宽高,需要字体名称和需要计算的字符串两个参数
//这里需要注意fontname是实际的字体文件名
//对于Arial字体为arial.ttf,加粗版本arialbd.ttf
//宋体则为simsun.ttc,0
static Dictionary<string, double> CalculateFontSize(String fontname,String text) {
if (fontname == null) { return new Dictionary<string, double>(); }
//获取系统字体的字体路径
string fontPath = Path.Combine("C:/WINDOWS/Fonts/", fontname);
//创建指定字体
Font font = new Font(BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED));
//创建文字段落
var tmp = new iTextSharp.text.Paragraph(text, font);
var basefont = tmp.Font.GetCalculatedBaseFont(true);
//计算段落的字体宽度
var width = basefont.GetWidthPoint(text, tmp.Font.CalculatedSize);
//计算段落的字体高度
var height = basefont.GetAscentPoint(text, tmp.Font.CalculatedSize) - basefont.GetDescentPoint(text, tmp.Font.CalculatedSize);
//以字典形式返回结果
Dictionary<string,double> fontSize = new Dictionary<string, double>() {
{ "width",width },
{ "height",height },
};
return fontSize;
}
计算出字体宽高后,则需要计算出新的 rect 坐标,写入 xfdf 文件中
rect坐标的读写
提取坐标
提取 rect 坐标可遍历所有 freetext 后采用 freetext.Attribute("rect").Value
获得其文本结果
计算坐标
对于 pdf 坐标系是以左下角为坐标轴原点,x 轴向右为正方向,y 轴向上为正方向,详细可参考:itextpdf确定页面坐标的方式
对于 rect 坐标
rect="x1,y1,x2,y2"
计算字符串宽度和高度只需要计算 x2,y1
即:rect="x1,y2-textHeight,x1+textWidth,y2"
这里计算 x2 和 y1 是考虑到文字左对齐且靠近顶部,因此只需要调整 x2 和 y1 的大小
同时需要考虑到文字与矩形边框有一定的边距,这里通过实验调整得到以下结果
//文字与矩形框之间剩余总宽度
private static double paddingWidth = 5d;
//文字与矩形框之间剩余总高度
private static double paddingHeight = 8.5d;
最后的rect坐标调整结果为
rect="x1,y2-(textHeight+paddingWidth),x1+(textWidth+paddingHeight),y2"
更新坐标
计算好坐标以后,即可使用 freetext.Attribute("rect").SetValue(rectString);
来更新坐标
保存XFDF文件
使用 document.Save('blank_new.xfdf');
保存新的 xfdf 文件即可。
最后将新的 xfdf 文件导入空白无注释的 pdf 文件中即可看到调整的效果
调整前
调整后
后话
起初最想到的是使用 Python 进行自动调整注释框,没想到在计算字体宽高一直无法得到准确的结果导致卡了很久。
后面采用 C# 的 iTextSharp 库做了个计算宽高的命令行程序,然后使用 Python 的 subprocess 调用返回结果,结果发现效率太太太低了,注释一多,运行就得几分钟,然后直接全改为 C# 编写,于是有了这一片文章。