开发者

C#实现一键批量合并PDF文档

目录
  • 前言
  • 效果展示
  • 功能实现
    • 1、添加文件
    • 2、文件分组(书签)
    • 3、定义页码范围
    • 4、自定义显示
    • 5、定义页面尺寸
    • 6、PDF批量合并
    • 7、其他
  • 方法补充

    前言

    由于项目需要编写大量的材料,以及各种签字表格、文书等,最后以PDF作为材料交付的文档格式,过程文档时有变化或补充,故此处理PDF文档已经成为日常工作的一部分。

    网上有各种PDF处理工具,总是感觉用得不跟手。

    最后回顾自己的需求总结为以下几项:

    1、可以便捷、快速的对多份PDF进行合并。

    2、可以从源PDF选取指定页码进行合并。

    3、可以从单个PDF提取特定页码(拆分PDF)。

    4、对多个PDF分组,合并作为最终PDF的导航书签,可快速定位。

    5、统一合成后PDF页面尺寸,如统一为A4幅面。

    6、操作尽量简便,支持文件拖放,不需要花巧的东西。

    效果展示

    首先,我们看看最终成品:

    C#实现一键批量合并PDF文档

    1、可以批量添加多个PDF到列表框中,也可以资料管理器将文件批量拖进来实现添加。

    2、[可选]定义分组标题对文件进行分组,也作为合并后PDF的书签。

    3、将列表中PDF批量合并到一个文件中。如果只有PDF,而且定义了页码范围,则转换为拆分功能。

    4、显示PDF总页数,如果只需提取部分内容,可以定义页码范围。

    5、可以更改合并后PDF页面的尺寸,统一为A4、B4或A5幅面。

    功能实现

    搜索发现github有个开源的

    github.com/schourode/pdfbinder

    比较android接近想要的效果,本着能省即省、成本最低、能效更高的原则,直接以此为基础进行扩展,开发自身所需的功能。

    1、添加文件

    这个比较简单,点击按钮后弹出选择对话框,将选择的文件逐一加到ListBox中。

    private void addFileButton_Click(object sender, EventArgs e)
    {
        if (addFileDialog.ShowDialog() == DialogResult.OK)
        {
            foreach (string file in addFileDialog.FileNames)
            {
                AddInputFile(file);
            }
            UpdateUI();
        }
    }
    

    其中AddInputFile函数单独编写是为了在拖放事件中复用。

    public void AddInputFile(string file)
    {
        int Pages = 0;
        switch (Combiner.TestSourceFile(file, out Pages))
        {
            case Combiner.SourceTestResult.Unreadable:
                MessageBox.Show(string.Format(resources.GetString("Error.Unreadable.Text"), file), resources.GetString("Error.Unreadable.Title"), MessageBoxButtons.OK, MessageBoxIcon.Error);
                break;
            case Combiner.SourceTestResult.Protected:
                MessageBox.Show(string.Format(resources.GetString("Error.Protected.Text"), file), resources.GetString("Error.Protected.Title"), MessageBoxButtons.OK, MessageBoxIcon.Hand);
                break;
            case Combiner.SourceTestResult.Ok:
                FileListBox.Items.Add(new PdfInfo() { Fullname = file, Filename = Path.GetFileName(file), Ranges = "", TotalPages = Pages });
                break;
        }
    }
    

    这里对PDF文件有效性进行了检查,而且添加到ListBox的是PdfInfo对象,它还记录了总页数、提取的页面范围等信息。

    文件拖放的实现:

    private void FileListBox_DragEnter(object sender, DragEventArgs e)
    {
        e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop, false) ? DragDropEffects.All : DragDropEffects.None;
    }
    private void FileListBox_DragDrop(object sender, DragEventArgs e)
    {
        var fileNames = (string[])e.Data.GetData(DataFormats.FileDrop);
        Array.Sort(fileNames);
        foreach (var file in fileNames)
        {
            AddInputFile(file);
        }
        UpdateUI();
    }
    

    2、文件分组(书签)

    using BookmarkName = System.String;
    private void addBookmarkButton_Click(object sender, EventArgs e)
    {
        //未添加文件不处理
        if (FileListBox.SelectedIndex < 0) return;
    
        //如果选择的书签(组名),读取名称供修改
        BookmarkName bookmark = "";
        if (FileListBox.SelectedItem is BookmarkName)
            bookmark = (BookmarkName)FileListBox.SelectedItem;
        else 
        {
            //如果选择的是文件,提取文件名作默认值
            bookmark = ((PdfInfo)FileListBox.SelectedItem).Filename;
            if (bookmark.Contains("."))
                bookmark = bookmark.Substring(0, bookmark.LastIndexOf("."));
        }
        //如果输入有效,添加书签(组名)
        BookmarkName newName = Interaction.InputBox(resources.GetString("SetBookmark.Prompt"), resources.GetString("SetB编程客栈ookmark.Title"), bookmark);
        if (newName != "")
        {
            if (FileListBox.SelectedItem is BookmarkName)
                FileListBox.Items[FileListBox.SelectedIndex] = newName;
            else
            {
                FileListBox.Items.Insert(FileListBox.SelectedIndex, newName);
                BookmarkCounter++;
            }
        }
    }
    

    3、定义页码范围

    没有定义页码范围表示整个PDF进行合并。定义了页面范围,合并时只提取相应的页面进行合并。

    页码范围的格式与常见的打印功能的页码定义相一致,如:1,2,3,6-9。

    这个操作放在右键弹出菜单中实现。

    private void mnuSetPageRange_Click(object sender, EventArgs e)
    {
        PdfInfo item = ((PdfInfo)FileListBox.SelectedItem);
        string range = Interaction.InputBox(resources.GetString("SetPageRange.Prompt"), resources.GetString("SetPageRange.Title"), item.Ranges);
        //内容未变更的不用处理
        if (range != item.Ranges)
        {
            if (range == "")
            {
                ((PdfInfo)FileListBox.Items[FileListBox.SelectedIndex]).Ranges = "";
                return;
            }
            //针对逗号和空格做处理
            string[] arr = range.Replace(",", ",").Replace(" ", "").Split(',');
            range = "";
            for (int i = 0; i < arr.Length; i++)
            {
                //用正则表达式判断有效性
                if ("" == arr[i]) continue;
                if (Regex.IsMatch(arr[i], @"^\d+$") || Regex.IsMatch(arr[i], @"^\d+-\d+$"))
                    range += ("" == range ? "" : ",") + arr[i];
                else
                {
                    MessageBox.Show(resources.GetString("Error.RangeValid")); 
                    return;
                }
            }
            //输入有效,更新
            ((PdfInfo)FileListBox.Items[FileListBox.SelectedIndex]).Ranges = range;
            UpdateUI();
        }
    }
    

    4、自定义显示

    为了在ListBox中显示书签、总页数和提取页码范围,需要接管ListBox的绘制事件。

    private void FileListBox_DrawItem(object sender, DrawItemEventArgs e)
    {
        ...
        StringFormat Formater = new StringFormat();
        Formater.Alignment = StringAlignment.Near;
        Formater.LineAlignment = StringAlignment.Center;
        Formater.Trimming = StringTrimming.EllipsisPath;
        Formater.FormatFlags = StringFormatFlags.NoWrap;
    
        //绘制书签(分组名)
        if (FileListBox.Items[e.Index] is BookmarkName)
        {
            //绘书签(分组名)图标
            e.Graphics.DrawImage(addBookmarkButton.Image, e.Bounds.X, e.Bounds.Y + ((e.Bounds.Height - addBookmarkButton.Image.Height) /2));
            //绘书签(分组名)
            e.Graphics.DrawString((BookmarkName)FileListBox.Items[e.Index], e.Font, Brushes.Black
                , new Rectangle(e.Bounds.X + addBookmarkButton.Image.Width, e.Bounds.Y, e.Bounds.Width - RIGHT_MARGIN, e.Bounds.Height), Formater);
            return;
        }
        //绘制PDF文件名
        PdfInfo item = (PdfInfo)FileListBox.Items[e.Index];
        e.Graphics.DrawString(showNameButton.androidChecked ? item.Fullname : item.Filename, e.Font, Brushes.Black
            , new Rectangle(e.Bounds.X + (BookmarkCounter > 0 ? (int)(addBookmarkButton.Image.Width * 1.5) : 0), e.Bounds.Y, e.Bounds.Width - RIGHT_MARGIN, e.Bounds.Height), Formater);
        //绘制页码
        Formater.Alignment = StringAlignment.Far;
        e.Graphics.DrawString((item.Ranges == "" ? "" : item.Ranges + " | ") 
            + string.Format(item.TotalPages>1 ? resources.GetString("Pages"): resources.GetString("Page"), item.TotalPages)
            , e.Font, Brushes.Gray, e.Bounds, Formater);
    }
    

    5、定义页面尺寸

    默认是原始尺寸(不做调整),可根据需要选择为A4、A5、B4。

    private void OnPageSizeChanged(object sender, EventArgs e)
    {
        PageSizeButton.Tag = ((ToolStripMenuItem)sender).Tag;
        mnuPageSize_Original.Checked = sender == mnuPageSize_Original;
        mnuPageSize_A4.Checked = sender == mnuPageSize_A4;
        mnuPageSize_A5.Checked = sender == mnuPageSize_A5;
        mnuPageSize_B4.Checked = sender == mnuPageSize_B4;
        if (mnuPageSize_Original.Checked)
            PageSizeButton.Text = resources.GetString("PageSizeButton.Text");
        else
            PageSizeButton.Text = resources.GetString("PageSizeButton.Text") + ":" + ((ToolStripMenuItem)sender).Text;
    }
    

    6、PDF批量合并

    这个比较长,有兴趣的可以到github.com/kacarton/PDFBinder2下载源码自己看,以下摘录核心部分。

    private void combineButton_Click(object sender, EventArgs e)
    {
        if (saveFileDialog.ShowDialog() == DialogResult.OK)
        {
            using (var combiner = new Combiner(saveFileDialog.FileName, (PDFBinder.PageSize)PageSizeButton.Tag))
            {
                progressBar.Visible = true;
                this.Enabled = false;
    
                for (int i = 0; 编程客栈i < FileListBox.Items.Count; i++)
                {
                    if (FileListBox.Items[i] is BookmarkName)
                        combiner.AddBookmark((string)FileListBox.Items[i]);
                    else
                        combiner.AddFile(((PdfInfo)FileListBox.Items[i]).Fullname, ((PdfInfo)FileListBox.Items[i]).Ranges);
                    //刷新进度
                    progressBar.Value = (int)(((i + 1) / (double)FileListBox.Items.Count) * 100);
                }
    
                this.Enabled = true;
                progressBar.Visible = false;
            }
    
            System.Diagnostics.Process.Start(saveFileDialog.FileName);
        }
    }
    
    class Combiner : IDisposable
    {
        public void AddFile(string fileName, string range)
        {
            var reader = new PdfReader(fileName);
            ....
            _document.Newpage();
                    
            //添加书签
            if (!string.IsNullOrEmpty(this.BookMarkName))
            { 
                Chapter _chapter = new Chapter("", 1);
                _chapter.BookmarkTitle = this.BookMarkName;
                _chapter.BookmarkOpen = true;
                _document.Add(_chapter);
                this.BookMarkName = null;
            }
    
            if (_newPageSize == PageSize.Original)
            {
                var page = _pdfCopy.GetImportedPage(reader, i);
                _pdfCopy.AddPage(page);
            }
            else
            {
                var page = _writer.GetImportedPage(reader, i);
                _document.Add(iTextSharp.text.Image.GetInstance(page));
            }
    
            reader.Close();
        }
    }
    

    7、其他

    UI同步、文件移除、上移、下移、排序、多语言支持这些比较简单就不展开了。

    方法补充

    C#使用Spire.PDF for .NET合并PDF

    为什么选择Spire.PDF for .NET

    Spire.PDF for .NET 是一款功能全面、性能卓越的PDF处理库,专为.NET平台设计。它允许开发者在C#、vb.net等语言中轻松创建、编辑、转换、打印和查看PDF文档,而无需安装Adobe AcroBAT等第三方软件。

    选择Spire.PDF for .NET进行PDF合并的主要原因包括:

    • 功能全面: 除了合并,它还支持PDF的拆分、加密、解密、内容提取、文本替换、添加水印、数字签名等多种操作。
    • 性能优异:www.devze.com处理大量或复杂的PDF文档时,Spire.PDF for .NET展现出卓越的稳定性和处理速度,有效提升开发效率。
    • 易于集成: 作为一个纯.NET组件,它可以无缝集成到各种.NET应用中,如Windows Forms、ASP.NET、WPF以及.NET Core项目。
    • 兼容性强: 支持从.NET Framework 2.0到.NET 5.0+的多个版本,并能处理从PDF 1.2到1.7的各种PDF版本。
    • 开发者友好: 提供清晰的API接口和丰富的示例,大大降低了学习曲线。

    环境准备:安装Spire.PDF for .NET

    在使用Spire.PDF for .NET之前,我们需要将其添加到项目中。最简便的方式是通过NuGet包管理器在Visual Studio中进行安装。

    安装步骤:

    • 打开Visual Studio。
    • 在“解决方案资源管理器”中,右键点击您的项目,选择“管理NuGet程序包...”。
    • 在“浏览”选项卡中,搜索“Spire.PDF”。
    • 找到“Spire.PDF”包,点击“安装”。

    您也可以通过NuGet包管理器控制台运行以下命令:

    Install-Package Spire.PDF

    安装完成后,Spire.PDF for .NET的引用将自动添加到您的项目中。

    核心实现:使用C#合并PDF文档的步骤与代码

    Spire.PDF for .NET 提供了简洁而强大的方法来合并PDF文档。以下是详细的步骤和示例代码:

    步骤列表:

    • 准备源PDF文件路径: 确定您要合并的所有PDF文件的完整路径。
    • 创建字符串数组: 将所有源PDF文件的路径存储在一个字符串数组中。
    • 调用 PdfDocument.MergeFiles 方法: 使用 PdfDocument.MergeFiles(string[] filePaths, string destFile) 静态方法一次性合并所有文件。这个方法会直接将合并后的PDF保存到指定的目标路径。

    示例代码块:

    using System;
    using Spire.Pdf;
    
    namespace MergePdfDocuments
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 1. 定义源PDF文件路径数组
                // 请将 "Document1.pdf", "Document2.pdf", "Document3.pdf" 替换为您的实际文件路径
                string[] sourceFiles = new string[]
                {
                    "C:\Users\YourUser\Desktop\Document1.pdf",
                    "C:\Users\YourUser\Desktop\Document2.pdf",
                    "C:\Users\YourUser\Desktop\Document3.pdf"
                };
    
                // 2. 定义合并后PDF文档的输出路径
                string outputFile = "C:\Users\YourUser\Desktop\MergedDocument.pdf";
    
                try
                {
                    // 3. 使用Spire.Pdf.PdfDocument.MergeFiles方法合并PDF文档
                    // 这个方法是静态的,可以直接调用,它会处理所有的合并逻辑并将结果保存到指定文件
                    PdfDocument.MergeFiles(sourceFiles, outputFile);
    
                    Console.WriteLine($"PDF文档已成功合并到:{outputFile}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"合并PDF文档时发生错误:{ex.Message}");
                }
    
                Console.ReadKey();
            }
        }
    }
    

    代码解释:

    • using Spire.Pdf; 导入了Spire.PDF库的命名空间,以便访问其提供的类和方法。
    • string[] sourceFiles 数组包含了所有待合并的PDF文件的完整路径。您可以根据需要添加或删除文件。
    • string outputFile 定义了合并后新PDF文档的保存路径和文件名。
    • PdfDocument.MergeFiles(sourceFiles, outputFile) 是核心方法。它接收一个字符串数组(包含所有源PDF路径)和一个目标文件路径,然后执行合并操作并将结果保存。
    • try-catch 块用于捕获可能发生的异常,确保程序的健壮性。

    到此这篇关于C#实现一键批量合并PDF文档的文章就介绍到这了,更多相关C#合并PDF内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜