Pdf文件处理组件对比(Aspose.Pdf,Spire.Pdf,iText7)
目的
因为公司是做医疗相关软件的,所以经常和文档打交道,其中就包含了Pdf。医院的Pdf(通常是他们的报告)都千奇百怪,而我们一直以来都是在用一些免费且可能已经没人维护了的组件来处理Pdf,所以就经常出现Pdf转乱码,甚至直接异常的情况。跟公司管理层反应了很久,终于答应掏腰包采购一款Pdf的处理组件,并且交由我来预研。稍微调查了一下后,最终商业组件选中了 Aspose,Spire还有Leadtool这三家公司的产品,另外由于iTextSharp作为开源的Pdf处理组件太有名,所以我也把它的重写版——iText7加入了对比的列表中,写了一个可以方便执行的Demo项目,想了想,为了那些同样需要Pdf相关资料的同学,也为了c# 系的开源生态,把这个项目特意整理了一下,发到了Github上,并且写了一些简单的使用说明。
GitHub的地址请戳 这里
概述
本项目主要以常见的五种Pdf处理来进行预研,包括“Pdf转Jpeg”,“Pdf转txt”,“Xps转Pdf”,“将jpeg作为插页插入到Pdf中”,“加水印”。
在对上面提出的四种组件进行充分调查后,直接否定了LeadTool,因为文档太少,转化质量也不行,官网下下来的Sample也乱得要死,所以本项目只包含了三个组件的实现 Aspose.Pdf,Spire.Pdf,iText7。
这里不得不提的就是iText虽然很有名,但功能还是不全的,至少我翻遍了文档也没找到把Pdf转图片和把Xps转Pdf这两个功能,如果有知道的同学麻烦留言说一声。
再来看看项目的设计,整个项目功能很简单,都是基于以上五个功能来实现的,所以我定出了这几个功能的接口,然后所有功能组件都基于这个接口可以灵活拓展不同的处理组件,下面的代码片段是功能接口的定义。
public interface IPdfComponentFunc { string ComponentName { get; } void ToJpeg(string absoluteFilePath, string outputPath); void ToTxt(string absoluteFilePath, string outputPath); void FromXps(string absoluteFilePath, string outputPath); void InsertPage(string absoluteFilePath, string outputPath); void AddWaterprint(string absoluteFilePath, string outputPath); }
功能相信看名字就知道了,然后接下来是三个不同组件的代码实现。
Aspose.Pdf(由于试用版只能转4页,一怒之下搞了个破解版)
public class AsposePdfComponent : IPdfComponentFunc { public string ComponentName => "Aspose.Pdf"; private const string Key = "PExpY2Vuc2U+DQogIDxEYXRhPg0KICAgIDxMaWNlbnNlZFRvPlNoYW5naGFpIEh1ZHVuIEluZm9ybWF0aW9uIFRlY2hub2xvZ3kgQ28uLCBMdGQ8L0xpY2Vuc2VkVG8+DQogICAgPEVtYWlsVG8+MzE3NzAxODA5QHFxLmNvbTwvRW1haWxUbz4NCiAgICA8TGljZW5zZVR5cGU+RGV2ZWxvcGVyIE9FTTwvTGljZW5zZVR5cGU+DQogICAgPExpY2Vuc2VOb3RlPkxpbWl0ZWQgdG8gMSBkZXZlbG9wZXIsIHVubGltaXRlZCBwaHlzaWNhbCBsb2NhdGlvbnM8L0xpY2Vuc2VOb3RlPg0KICAgIDxPcmRlcklEPjE2MDkwMjAwNDQwMDwvT3JkZXJJRD4NCiAgICA8VXNlcklEPjI2NjE2NjwvVXNlcklEPg0KICAgIDxPRU0+VGhpcyBpcyBhIHJlZGlzdHJpYnV0YWJsZSBsaWNlbnNlPC9PRU0+DQogICAgPFByb2R1Y3RzPg0KICAgICAgPFByb2R1Y3Q+QXNwb3NlLlRvdGFsIGZvciAuTkVUPC9Qcm9kdWN0Pg0KICAgIDwvUHJvZHVjdHM+DQogICAgPEVkaXRpb25UeXBlPkVudGVycHJpc2U8L0VkaXRpb25UeXBlPg0KICAgIDxTZXJpYWxOdW1iZXI+NzM4MDNhYmUtYzZkMi00MTY3LTg2MTgtN2I0NDViNDRmOGY0PC9TZXJpYWxOdW1iZXI+DQogICAgPFN1YnNjcmlwdGlvbkV4cGlyeT4yMDE3MDkwNzwvU3Vic2NyaXB0aW9uRXhwaXJ5Pg0KICAgIDxMaWNlbnNlVmVyc2lvbj4zLjA8L0xpY2Vuc2VWZXJzaW9uPg0KICAgIDxMaWNlbnNlSW5zdHJ1Y3Rpb25zPmh0dHA6Ly93d3cuYXNwb3NlLmNvbS9jb3Jwb3JhdGUvcHVyY2hhc2UvbGljZW5zZS1pbnN0cnVjdGlvbnMuYXNweDwvTGljZW5zZUluc3RydWN0aW9ucz4NCiAgPC9EYXRhPg0KICA8U2lnbmF0dXJlPm5LNVVUR3dZMWVJSEtIV0d2NW5sQUxXUy81bDEzWkFuamlvdnlBcGNqQis0ZjNGbm5yOWhjeUlzazlvVzQySWp0ZFYra2JHZlNSMUV4OUozSGlkaThCeE43aHFiR1BERXNaWGo2RlYxaGl1N2MxWmUyNEp3VGc2UnpsNUNJRHY1YVhxbDQyczBkSGw4eXpreDRBM2RTTU5KTzRiQ094a2V2OFBiOWxSaUc3ST08L1NpZ25hdHVyZT4NCjwvTGljZW5zZT4="; private static Stream LStream = (Stream)new MemoryStream(Convert.FromBase64String(Key)); public AsposePdfComponent() { SetPdfLicense(); } public void SetPdfLicense() { var l = new Aspose.Pdf.License(); //l.SetLicense(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Aspose.Total.lic")); l.SetLicense(LStream); } public void AddWaterprint(string absoluteFilePath, string outputPath) { var watermarkImgPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DefaultResource", "waterMarkImg.jpeg"); using (var pdfDocument = new Document(absoluteFilePath)) using (BackgroundArtifact background = new BackgroundArtifact()) { foreach (Page aPdfPage in pdfDocument.Pages) { ImageStamp imageStamp = new ImageStamp(watermarkImgPath); imageStamp.XIndent = 300; imageStamp.YIndent = aPdfPage.PageInfo.Height - 200; imageStamp.Height = 81; imageStamp.Width = 80; aPdfPage.AddStamp(imageStamp); } pdfDocument.Save(outputPath); } } public void FromXps(string absoluteFilePath, string outputPath) { // Instantiate LoadOption object using XPS load option Aspose.Pdf.LoadOptions options = new XpsLoadOptions(); // Create document object Aspose.Pdf.Document document = new Aspose.Pdf.Document(absoluteFilePath, options); // Save the resultant PDF document document.Save(outputPath); } public void InsertPage(string absoluteFilePath, string outputPath) { var insertPageImgPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DefaultResource","insertPage.jpeg"); using (var pdfDocument = new Document(absoluteFilePath)) using (BackgroundArtifact background = new BackgroundArtifact()) { // Add a new page to document object Page page = pdfDocument.Pages.Insert(2); page.AddImage(insertPageImgPath, page.Rect); pdfDocument.Save(outputPath); } } public void ToJpeg(string absoluteFilePath, string outputPath) { using (var pdfDocument = new Document(absoluteFilePath)) { for(var i = 1; i < pdfDocument.Pages.Count + 1; i++) { using (FileStream imageStream = new FileStream(Path.Combine(outputPath, i.ToString() + ".jpeg"), FileMode.Create)) { //Quality [0-100], 100 is Maximum //create Resolution object Resolution resolution = new Resolution(300); JpegDevice jpegDevice = new JpegDevice(resolution, 100); //convert a particular page and save the image to stream jpegDevice.Process(pdfDocument.Pages[i], imageStream); //close stream imageStream.Close(); } } } } public void ToTxt(string absoluteFilePath, string outputPath) { var txtAbsorber = new TextAbsorber(); using (var pdfDocument = new Document(absoluteFilePath)) { pdfDocument.Pages.Accept(txtAbsorber); File.WriteAllText(outputPath, txtAbsorber.Text); } } }
不想代码篇幅太长,所以另外两个组件的代码实现不贴了。
当你每实现一个接口,程序都会自动把它添加到组件(UseComponent)的下拉列表中,如下图所示。
因为考虑现在开源生态国际化,所以都写的英文,本人比较懒,也就没做多语言。
Demo使用方法Github上都有写明,这里只贴一下运行后的界面图
使用组件:Aspose,执行次数:10,耗时:27473 毫秒
结尾语
简单地说下我这边的一个预研结果,我这里测试了一堆以前有问题的Pdf,最终发现:
Aspose.Pdf:1,从Xps转到Pdf的功能存在某些缺陷问题,有些Xps文档直接抛了异常。2,转Pdf到Image的功能对系统字体库有依赖,如果缺少Pdf文件的字体,转出来都是乱码。已经联系他们的技术团队看是否能改善 这两个问题,我就会选用它,目前在我这边印象最好。
Spire.Pdf:1,Pdf转Image的速度要比Aspose快,不过遗憾的是,带有图片的Pdf转Image直接抛了异常。2,转Txt的格式比起Aspose要差很多,但内容没什么毛病。
iText7:1,跟Spire一样,带图片的Pdf处理起来直接抛异常,而且iText没有Xps转Pdf和转Jpeg功能(我翻遍了文档没发现)
值得一提的是,速度最快的是iText7,虽然它功能不全,其次是Spire,不过Aspose转出来的东西很少会出问题,Txt的格式基本还原Pdf的原格式,反观Spire和iText这点就很不好,全部密密麻麻排一块去了。
这里只实现了我觉得比较好的三个组件来对比,如果有同学有其他更好的组件麻烦推荐下,我可以添加到组件中去,你也可以直接到Github上去Fork这份代码然后拓展自己的组件进行研究。
最近把自己网站重构成了.net core2.0,真的感觉到微软的良苦用心,微软都这么推行开源了,作为从c#出身,现在却在带Web组的人只希望C#系的开源生态能越来越好,不然我就要投奔NodeJs了(笑)。