自製 PDF 檔案合併小工具
| | 7 | | ![]() |
這年頭要合併 PDF,現成軟體或線上服務多如牛毛。考量線上服務必須上傳 PDF 檔到雲端我不愛,免費又順手的軟體得花時間尋找評估,想到就懶。不寫程式碼手會癢的我,決定省下找軟體學軟體的時間,依據使用習慣自己寫工具來用,這才符合程式硬漢作風。(謎之聲:可憐吶,看來病得不輕呀)
講到用 .NET 合併 PDF 檔,過去我研究過 PdfPig、PdfSharp / PdfSharpCore、Telerik PDF,每一套都能勝任。評估後我選擇只需單一組件檔(dll)的老牌 .NET PDF 元件 - PdfSharp (.NET Framework) / PdfSharpCore (.NET 6+)。
這些年下來,隨著愈來愈常開終端機操作 Linux、用 PowerShell 管理及部署 IIS,加上用 git、dotnet 下指令已是家常便飯,我已愛上用 CLI 敲指令,不複雜的操作根本不想開啟 GUI。因此,我理想中的 PDF 合併工具,也應該寫成 CLI,用起來像這樣:
LitePdfMerger sample.pdf prince\invoice.pdf prince\textbook.pdf
LitePdfMerger sample.pdf prince\*.pdf
LitePdfMerger sample.pdf prince\*.pdf -o:merged.pdf
專案我選用 .NET 8 Console,並打算啟用 Native AOT 編譯成輕巧的單一原生 EXE 檔,不需安裝 .NET 8 Runtime 或 SDK,Copy-and-Play。
要啟用 Native AOT,.csproj 需加入 PublishAot 及 IsAotCompatible。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PdfSharpCore" Version="1.3.63" />
</ItemGroup>
</Project>
程式碼不多,不到 70 行搞定。(註:其中有用到 Console.ReadLine() 偵測 Ctrl-C 中斷的技巧)
using PdfSharpCore.Pdf;
using PdfSharpCore.Pdf.IO;
Console.WriteLine("PDF Merge Tools v0.9b");
List<PdfDocument> sources = new List<PdfDocument>();
var outputPath = string.Empty;
foreach (string fn in args)
{
if (fn.StartsWith("-o:"))
{
outputPath = fn.Substring(3);
continue;
}
if (fn.Contains("*"))
{
var folder = Path.GetDirectoryName(fn);
if (string.IsNullOrEmpty(folder)) folder = ".";
foreach (var found in Directory.GetFiles(folder, Path.GetFileName(fn), SearchOption.AllDirectories))
{
Console.WriteLine($" * {found}");
var srcPdf = PdfReader.Open(found, PdfDocumentOpenMode.Import);
sources.Add(srcPdf);
}
}
else if (!File.Exists(fn))
{
Console.WriteLine("Can't find " + fn);
return;
}
else
{
Console.WriteLine(" * " + fn);
var srcPdf = PdfReader.Open(fn, PdfDocumentOpenMode.Import);
sources.Add(srcPdf);
}
}
if (sources.Count == 0)
{
Console.WriteLine("No pdf to merge");
return;
}
var merged = new PdfDocument();
foreach (var pdf in sources)
{
for (int i = 0; i < pdf.PageCount; i++)
{
merged.AddPage(pdf.Pages[i]);
}
}
var mergedFilePath = $"{DateTime.Now:yyyyMMdd-HHmmss}.pdf";
if (!string.IsNullOrEmpty(outputPath))
{
mergedFilePath = outputPath;
}
else
{
Console.Write($"Output filename [default: {mergedFilePath}]: ");
var assignFilePath = Console.ReadLine();
if (assignFilePath == null) return; // user press Ctrl+C or Ctrl-Z
Console.WriteLine("assignFilePath: " + assignFilePath);
if (!string.IsNullOrEmpty(assignFilePath))
{
if (!assignFilePath.EndsWith(".pdf")) assignFilePath += ".pdf";
mergedFilePath = assignFilePath;
}
}
Console.WriteLine($"Saving to {mergedFilePath}");
merged.Save(mergedFilePath);
編譯出來的 EXE 檔很小,大約 3.3MB,複製到其他電腦可以直接用,不需要先安裝 .NET 8,這是 .NET Native AOT 技術的一大優勢。
我從網路上找了一些 PDF 範例檔來做測試:
測試一,輸入要合併的 PDF 路徑合併成單一 PDF,懶得想輸出檔名可按 Enter 接受用日期時間自動命名,或者可輸入合併檔名。
測試二,來源 PDF 路徑還支援 * 萬用字元,並可用 -o:outputPath
事先指定輸出檔名,方便批次作業。
實測成功!
範例專案我放上 Github 了,大家若已安裝 Git for Windows 跟 .NET 8 SDK,執行以下指令便能做出這個好用的 PDF 合併小工具。
git clone https://github.com/darkthread/LitePdfMerger.git
cd LitePdfMerger
dotnet publish -c Release
dir .\bin\Release\net8.0\win-x64\publish\
This post is about creating a custom CLI tool in .NET to merge PDF files using PdfSharpCore. I explain the benefits of using a CLI tool over online services and provides a detailed implementation, including project setup and code. The tool leverages .NET 8 and Native AOT for lightweight, standalone execution.
Comments
# by Sephi
typo, 編譯出來的 EXE 檔很小,大約 "3.3MB" :)
# by Jeffrey
to Sephi, 謝謝,已修正。
# by Alex
Typo, 我已愛上用 CLI 敲指令,不複雜的操作根本(不)想開 GUI。—少了個(不)
# by Hank
請問下 要編譯 Linux 用的 Native AOT 原生程式庫需要用 Linux 平台上編譯(編譯平台與目標平台需相同) 這也包含 CPU 架構對嗎? 例如想要在 Windows Arm 架構跑,不能用 Windows x64 編譯?
# by Jeffrey
to Alex, 哈,對,謝謝~
# by Jeffrey
to Hank, 是的,編譯 Native AOT 需要有 C++ 編譯環境,對作業系統依賴很深,甚至你用 Ubuntu 20.04 編譯的 Native AOT 檔案,只能在 Ubuntu 20.04 以後的作業系統執行,無法在 Ubuntu 18.04 用,對 Windows 不同 CPU 架構也是如此。
# by yoyo
不知道以後會不會有Native AOT 的cross compiler 哈