展示如何使用 C# 僅依賴 .NET Framework 基礎類庫 (System.IO、System.Text),手動構造符合 PDF 1.1 規範的文件流。適合無法引入第三方庫 (iTextSharp、PdfSharp) 的受限環境。
最小 PDF 包含:
%PDF-1.1
using System;
using System.IO;
using System.Text;
using System.Collections;
namespace PDFGenerator
{
public class PDFGenerator
{
static float pageWidth = 594.0f; // A4 寬 (pt)
static float pageDepth = 828.0f; // A4 高
static float pageMargin = 30.0f;
static float fontSize = 20.0f;
static float leadSize = 10.0f;
static StreamWriter pPDF = new StreamWriter("E://myPDF.pdf");
static MemoryStream mPDF = new MemoryStream();
static void ConvertToByteAndAddtoStream(string strMsg)
{
Byte[] buffer = ASCIIEncoding.ASCII.GetBytes(strMsg);
mPDF.Write(buffer, 0, buffer.Length);
}
static string xRefFormatting(long xValue)
{
string strMsg = xValue.ToString();
int iLen = strMsg.Length;
if (iLen < 10)
{
StringBuilder s = new StringBuilder();
s.Append('0', 10 - iLen);
strMsg = s.ToString() + strMsg;
}
return strMsg;
}
static void Main(string[] args)
{
ArrayList xRefs = new ArrayList();
float yPos = 0f;
long streamStart = 0, streamEnd = 0, streamLen = 0;
string strPDFMessage = null;
// 1. Header
strPDFMessage = "%PDF-1.1\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 2. Object 1: Content Stream (頁面內容)
xRefs.Add(mPDF.Length);
strPDFMessage = "1 0 obj\n<< /Length 2 0 R >>\nstream\n";
ConvertToByteAndAddtoStream(strPDFMessage);
streamStart = mPDF.Length;
// 繪圖指令
strPDFMessage = "BT\n/F0 " + fontSize + " Tf\n";
ConvertToByteAndAddtoStream(strPDFMessage);
yPos = pageDepth - pageMargin;
strPDFMessage = pageMargin + " " + yPos + " Td\n";
ConvertToByteAndAddtoStream(strPDFMessage);
strPDFMessage = leadSize + " TL\n";
ConvertToByteAndAddtoStream(strPDFMessage);
strPDFMessage = "(https://www.wenhui.org)Tj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
strPDFMessage = "ET\n";
ConvertToByteAndAddtoStream(strPDFMessage);
streamEnd = mPDF.Length;
streamLen = streamEnd - streamStart;
strPDFMessage = "endstream\nendobj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 3. Object 2: Length of stream 1
xRefs.Add(mPDF.Length);
strPDFMessage = "2 0 obj\n" + streamLen + "\nendobj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 4. Object 3: Page
xRefs.Add(mPDF.Length);
strPDFMessage = "3 0 obj\n<< /Type /Page /Parent 4 0 R /Contents 1 0 R >>\nendobj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 5. Object 4: Pages
xRefs.Add(mPDF.Length);
strPDFMessage = "4 0 obj\n<< /Type /Pages /Count 1\n/Kids[\n3 0 R\n]\n/Resources << /ProcSet [/PDF /Text] /Font << /F0 5 0 R >> >>\n/MediaBox [ 0 0 " + pageWidth + " " + pageDepth + " ]\n>>\nendobj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 6. Object 5: Font
xRefs.Add(mPDF.Length);
strPDFMessage = "5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>\nendobj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 7. Object 6: Catalog
xRefs.Add(mPDF.Length);
strPDFMessage = "6 0 obj\n<< /Type /Catalog /Pages 4 0 R >>\nendobj\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 8. xref 表
streamStart = mPDF.Length;
strPDFMessage = "xref\n0 7\n0000000000 65535 f \n";
ConvertToByteAndAddtoStream(strPDFMessage);
for (int i = 0; i < xRefs.Count; i++)
{
strPDFMessage = xRefFormatting((long)xRefs[i]) + " 00000 n \n";
ConvertToByteAndAddtoStream(strPDFMessage);
}
// 9. Trailer
strPDFMessage = "trailer\n<<\n/Size " + (xRefs.Count + 1) + "\n/Root 6 0 R\n>>\n";
ConvertToByteAndAddtoStream(strPDFMessage);
// 10. Startxref & EOF
strPDFMessage = "startxref\n" + streamStart + "\n%%EOF\n";
ConvertToByteAndAddtoStream(strPDFMessage);
mPDF.WriteTo(pPDF.BaseStream);
mPDF.Close();
pPDF.Close();
}
}
}
| 環節 | 說明 |
|---|---|
| 坐標系統 | 原點左下角,單位 Point (1/72 inch)。Td 移動文本位置。 |
| 字體 | 使用標準 14 款 BaseFont (Courier/Helvetica/Times-Roman 等),無需嵌入。 |
| 內容流 | BT...ET 文本對象,Tf 設置字體大小,Tj 顯示字符串。 |
| xref 格式 | 10 位數字偏移 + 空格 + 5 位生成號 + 空格 + n/f + 換行。 |
| 換行符 | PDF 規範要求 \n (LF),代碼中用 \n 而非 \r\n。 |
/Count 和 /Kids。/Filter /FlateDecode (zlib)。本文提供了零依賴生成基礎 PDF的完整 C# 實現,通過手動構造 PDF 1.1 結構體驗底層格式。理解此原理有助於調試 PDF 輸出問題、實現極簡環境下的報表導出,但複雜場景請使用專業庫。