第06回 簡単なPDFファイルを作成する


今回はPDFを作成します。
PDFは1ページのみで、内容は半角英数字のテキストのみ、フォントの埋め込みもなしです。

構成内容がこれだけの簡単なPDFです。

・PDFヘッダー
・フォントオブジェクト
・カタログオブジェクト(/Type /Catalog)
・ページインデックスオブジェクト(/Type /Pages)
・ページコンテンツインデックスオブジェクト(/Type /Page)
・ページコンテンツオブジェクト(stream情報)
・クロスリファレンス
・トレーラー
・PDFヘッダー

各所でオブジェクトのインデックスが参照されるので、nObjIndexとして管理。
クロスリファレンス作成用に_listnXrefを用意して、各オブジェクトを書き込む前にそのseekポジションを保存しました。
コンテンツ内容のstreamは圧縮する必要はないのですが、今後のことも考えてDeflateStreamで圧縮しています。

---
まずは別に作らなくていいのですが、PDFRawReaderを作ってしまったので、それに合わせて、PDFRawWriterクラスを作ります。
「プロジェクト」メニューから「クラスの追加」を選択し、「PDFRawWriter.cs」というクラスを追加。

同様にPDFTextReaderに合わせて、PDFTextWriterクラスを作ります。
「プロジェクト」メニューから「クラスの追加」を選択し、「PDFTextWriter.cs」というクラスを追加。

最後にForm1.csからPDFTextWriter.CreatePDFFile()を呼び出すようにすれば完成です。

■PDFRawWriter.cs
using System.IO;
using System.Text;

namespace Pdf2Pdf
{
	class PDFRawWriter : BinaryWriter
	{
		public PDFRawWriter(Stream strm)
			: base(strm)
		{
		}


		/// <summary>
		/// asciiエンコーディングで書き込む!
		/// </summary>
		public void WriteLine(string strText)
		{
			byte[] data = Encoding.ASCII.GetBytes(strText);
			Write(data);
			Write0x0a();
		}


		public void Write0x0a()
		{
			Write('\r');
		}
	}
}

■PDFTextWrite.cs
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace Pdf2Pdf
{
	class PDFTextWriter
	{
		enum FONT_TYPE
		{
			ASCII,
		}



		//クロスリファレンス生成用データ(各オブジェクトの位置を保存)
		List<long> _listnXref = new List<long>();

		//フォントの種類保存用
		IDictionary<string, FONT_TYPE> _mapFontType = new Dictionary<string, FONT_TYPE>();



		public void Close()
		{
			_listnXref.Clear();
			_mapFontType.Clear();
		}


		public bool CreatePDFFile(string strFile)
		{
			Close();

			int nObjIndex = 1;

			using (FileStream fs = new FileStream(strFile, FileMode.Create, FileAccess.Write))
			using (PDFRawWriter bw = new PDFRawWriter(fs))
			{
				//ヘッダー出力
				bw.WriteLine("%PDF-1.7");

				//フォント出力
				int nResourceIndex;
				{
					nResourceIndex = nObjIndex;
					WriteFont_Ascii(bw, ref nObjIndex, "Times-Italic", "F0");
				}

				//カタログ出力
				int nRoot = nObjIndex;
				{
					WriteCatalog(bw, ref nObjIndex, nObjIndex + 1);
				}

				//ページ出力
				{
					List<int> listPage = new List<int>();

					//全ページのインデックスを出力
					int nPagesReferenceIndex = nObjIndex;
					{
						listPage.Add(nObjIndex + 1);				//1ページ目のインデックスを渡す
						WritePages(bw, ref nObjIndex, listPage);
					}


					//1ページ出力
					{
						WritePageContentsIndex(bw, ref nObjIndex, nPagesReferenceIndex, nResourceIndex, nObjIndex + 1, 595, 842);

						//ページテキスト出力
						{
							List<PDFText> listTexts = new List<PDFText>();

							listTexts.Add(new PDFText("F0", 40, 50, 540, @"abc123"));


							//コンテンツの書き出し
							using (MemoryStream ms = new MemoryStream())
							using (BinaryWriter bwms = new BinaryWriter(ms))
							{
								//文字データをPDF出力用に準備
								PrepareTextContents(ms, listTexts);

								ms.Flush();

								byte[] data = ms.ToArray();

								//ページコンテンツの出力
								WriteFlateData(bw, ref nObjIndex, data);
							}
						}
					}
				}

				//クロスリファレンス/トレーラー出力
				WriteXrefTrailer(bw, nRoot);
			}

			return true;
		}



		/// <summary>
		/// カタログの書き込み
		/// 
		/// nPagesObjIndex は、WritePages()のnObjIndexを指定
		/// </summary>
		void WriteCatalog(PDFRawWriter bw, ref int nObjIndex, int nPagesObjIndex)
		{
			_listnXref.Add(bw.BaseStream.Position);
			{
				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Type /Catalog");
				bw.WriteLine("/Pages " + nPagesObjIndex + " 0 R");
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;
		}


		/// <summary>
		/// ページ情報の書き込み
		/// 
		/// listnKidsObjIndex は、WritePageContentsIndex()へのインデックス。すべてのページ分を用意する
		/// </summary>
		void WritePages(PDFRawWriter bw, ref int nObjIndex, List<int> listnKidsObjIndex)
		{
			_listnXref.Add(bw.BaseStream.Position);
			{
				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Type /Pages");

				string strKidLine = "/Kids [";
				foreach (int nKidObj in listnKidsObjIndex)
				{
					strKidLine += "" + nKidObj + " 0 R ";
				}
				strKidLine += "]";
				bw.WriteLine(strKidLine);

				bw.WriteLine("/Count " + listnKidsObjIndex.Count);
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;
		}



		/// <summary>
		/// 欧文フォントの指定
		/// 
		/// デフォルトで用意されているフォントを利用する場合はこれで指定、
		/// 使えるフォント名(strFont)は↓
		/// Times-Roman
		/// Helvetica
		/// Courier
		/// Symbol
		/// Times-Bold
		/// Helvetica-Bold
		/// Courier-Bold
		/// ZapfDingbats
		/// Times-Italic
		/// Helvetica-Oblique
		/// Courier-Oblique
		/// Times-BoldItalic
		/// Helvetica-BoldOblique
		/// Courier-BoldOblique
		/// </summary>
		void WriteFont_Ascii(PDFRawWriter bw, ref int nObjIndex, string strFont, string strFontObjName)
		{
			_listnXref.Add(bw.BaseStream.Position);
			{
				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Font");
				bw.WriteLine("<</" + strFontObjName);
					bw.WriteLine("<<");
						bw.WriteLine("/Type /Font");
						bw.WriteLine("/BaseFont /" + strFont);
						bw.WriteLine("/Subtype /Type1");
					bw.WriteLine(">>");
				bw.WriteLine(">>");
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;

			//フォント情報の保存
			_mapFontType.Add(strFontObjName, FONT_TYPE.ASCII);
		}



		void WritePageContentsIndex(PDFRawWriter bw, ref int nObjIndex, int nParentObjIndex, int nResourcesObjIndex, int nContentsObjIndex, float fWidth, float fHeight)
		{
			_listnXref.Add(bw.BaseStream.Position);
			{
				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Type /Page");
				bw.WriteLine("/Parent " + nParentObjIndex + " 0 R");
				bw.WriteLine("/Resources " + nResourcesObjIndex + " 0 R");
				bw.WriteLine("/MediaBox [0 0 " + fWidth + " " + fHeight + "]");
				bw.WriteLine("/Contents " + nContentsObjIndex + " 0 R");
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;
		}







		/// <summary>
		/// 与えられたテキストデータをストリームに以下の形式で書き込む
		/// "BT /F0 10 Tf 150 200 Td (hello) Tj ET\r";
		/// </summary>
		void PrepareTextContents(Stream strm, List<PDFText> listTexts)
		{
			using (MemoryStream ms = new MemoryStream())
			using (BinaryWriter bwms = new BinaryWriter(ms))
			{
				foreach (PDFText text in listTexts)
				{
					bool ret;
					FONT_TYPE type;

					ret = _mapFontType.TryGetValue(text.strFont, out type);
					if (ret == false)
					{
						//フォントの種類特定失敗
						Debug.Assert(false);
						continue;
					}


					string strText = text.strText;

					byte[] raw2 = null;

					if (type == FONT_TYPE.ASCII)
					{
						//カッコと¥をエスケープ
						strText = strText.Replace(@"\", @"\\");
						strText = strText.Replace(@"(", @"\(");
						strText = strText.Replace(@")", @"\)");

						raw2 = Encoding.ASCII.GetBytes(strText);
					}
					else
					{
						Debug.Assert(false);
					}

					string strData = "BT /" + text.strFont + " " + text.fPoint + " Tf " + text.fX + " " + text.fY + " Td (";
					byte[] raw1 = Encoding.ASCII.GetBytes(strData);
					byte[] raw3 = Encoding.ASCII.GetBytes(") Tj ET");

					bwms.Write(raw1);
					bwms.Write(raw2);
					bwms.Write(raw3);
					bwms.Write('\r');
				}
				ms.Flush();

				byte[] data = ms.ToArray();
				strm.Write(data, 0, data.Length);
			}
		}


		/// <summary>
		/// pcbRawData をCompressDeflate()にかけてstreamとして書き込む
		/// (pcbRawDataは圧縮せずに生データを渡すこと!)
		/// </summary>
		void WriteFlateData(PDFRawWriter bw, ref int nObjIndex, byte[] pcbRawData)
		{
			_listnXref.Add(bw.BaseStream.Position);
			{
				int nLength;

				byte[] data = CompressDeflate(pcbRawData);
				nLength = data.Length + 2;

				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Filter /FlateDecode /Length " + nLength + ">>");
				bw.WriteLine("stream");
				{
					byte[] header = new byte[2];
					header[0] = 0x78;
					header[1] = 0x9c;
					bw.Write(header);
				}
				bw.Write(data);
				bw.Write0x0a();
				bw.WriteLine("endstream");
				bw.WriteLine("endobj");
			}
			nObjIndex++;
		}



		/// <summary>
		/// クロスリファレンス/トレーラー/PDFフッターの書き出し
		/// </summary>
		void WriteXrefTrailer(PDFRawWriter bw, int nRootObjIndex)
		{
			//Trailerは1行のバイト数が重要

			long nXrefPos = bw.BaseStream.Position;

			bw.WriteLine("xref");
			bw.WriteLine("0 " + (_listnXref.Count + 1));
			bw.WriteLine("0000000000 65535 f ");

			foreach (long n in _listnXref)
			{
				bw.WriteLine(string.Format("{0:0000000000}", n) + " 00000 n ");
			}

			bw.WriteLine("trailer");
			bw.WriteLine("<<");
			bw.WriteLine("/Root " + nRootObjIndex + " 0 R");
			bw.WriteLine("/Size " + (_listnXref.Count + 1));
			bw.WriteLine(">>");

			bw.WriteLine("startxref");
			bw.WriteLine("" + nXrefPos);
	
			bw.WriteLine("%%EOF");
		}


		/// <summary>
		/// Deflateを圧縮する
		/// </summary>
		byte[] CompressDeflate(byte[] data)
		{
			byte[] ret = new byte[0];

			using (MemoryStream ms = new MemoryStream())
			{
				DeflateStream ds = new DeflateStream(ms, CompressionMode.Compress);

				ds.Write(data, 0, data.Length);
				ds.Flush();
				ds.Close();

				ret = ms.ToArray();
				ds.Dispose();
			}

			return ret;
		}
	}
}
■Form1.cs
		public Form1()
		{
			InitializeComponent();

			PDFTextReader pr = new PDFTextReader();
			bool ret = pr.Read(@"c:\N9442CW.pdf");

			//以下を追加
			PDFTextWriter pw = new PDFTextWriter();
			pw.CreatePDFFile("test.pdf");

			PictureBox pictureBox1 = new PictureBox();
			pictureBox1.Parent = this;
			pictureBox1.Dock = DockStyle.Fill;

プロジェクトファイルをダウンロード


カテゴリー「PDFを処理する(C#)」 のエントリー