Loading

基于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确定页面坐标的方式

pdf坐标轴示意图

对于 rect 坐标

rect="x1,y1,x2,y2"

rect 坐标

计算字符串宽度和高度只需要计算 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# 编写,于是有了这一片文章。

posted @ 2023-06-26 16:16  TicklTock  阅读(115)  评论(0编辑  收藏  举报