首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 純代碼生成 PDF 文件:無第三方庫實現基礎 PDF 創建

     編輯:關於C語言

前言

展示如何使用 C# 僅依賴 .NET Framework 基礎類庫 (System.IO、System.Text),手動構造符合 PDF 1.1 規範的文件流。適合無法引入第三方庫 (iTextSharp、PdfSharp) 的受限環境。

核心 PDF 結構

最小 PDF 包含:

  1. Header:%PDF-1.1
  2. Body:間接對象 (Object)
    • Catalog (根對象) → Pages
    • Pages → Kids (Page 列表)
    • Page → Parent, Contents (內容流), Resources (字體等), MediaBox
    • Font → Type1, BaseFont, Encoding
    • Content Stream → 繪圖指令 (BT/ET, Tf, Td, Tj)
  3. Cross-Reference Table (xref):對象偏移索引
  4. Trailer:根對象引用、對象總數
  5. Startxref:xref 表偏移

完整可運行代碼


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。

擴展方向

  • 多頁:增加 Page 對象,更新 Pages 的 /Count 和 /Kids。
  • 中文:需嵌入 TrueType 字體 (CIDFont),構造 ToUnicode CMap,複雜度大增。
  • 圖片:需解碼 JPEG/PNG,生成 Image XObject,處理 DCTDecode/FlateDecode。
  • 壓縮:內容流用 /Filter /FlateDecode (zlib)。

局限性提示

  • 僅支持 ASCII / WinAnsiEncoding 字符。
  • 無文字換行、排版、向量繪圖、表單、簽名等高級功能。
  • 生產環境強烈建議使用成熟庫 (PdfSharp, iText 7, QuestPDF)。

總結

本文提供了零依賴生成基礎 PDF的完整 C# 實現,通過手動構造 PDF 1.1 結構體驗底層格式。理解此原理有助於調試 PDF 輸出問題、實現極簡環境下的報表導出,但複雜場景請使用專業庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved