第12回 PDF内で複数のフォントを扱う

これまではPDF内のテキストは1つのフォントのみ利用可能でした。
今回は複数のフォントを利用できるようにします。

ついでに書き出すページも2ページに増やしておきました。
ここまで処理ができればツールの作成も目前です。

■PDFTextWriter.cs
		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;


					//フォント埋め込み
					{
						List<KeyValuePair<string, int>> listFonts = new List<KeyValuePair<string, int>>();

						{
							string strFontObjName = "F0";
							listFonts.Add(new KeyValuePair<string, int>(strFontObjName, nObjIndex));
							WriteFont_EmbeddedUnicode(bw, ref nObjIndex, strFontObjName, "MS-Gothic", "MS Gothic", @"msgothic.otf");
						}
						{
							string strFontObjName = "F1";
							listFonts.Add(new KeyValuePair<string, int>(strFontObjName, nObjIndex));
							WriteFont_Ascii(bw, ref nObjIndex, "Times-Italic", strFontObjName);						//欧文フォント指定
						}
						{
							string strFontObjName = "F2";
							listFonts.Add(new KeyValuePair<string, int>(strFontObjName, nObjIndex));
							WriteFont_UnicodeJapanese(bw, ref nObjIndex, "KozMinPr6N-Regular", strFontObjName);		//日本語フォント指定(フォント埋め込みなし)
						}

						//埋め込みフォント一覧のみのリソース
						nResourceIndex = nObjIndex;
						_listnXref.Add(bw.BaseStream.Position);
						{
							bw.WriteLine("" + nObjIndex + " 0 obj");
							bw.WriteLine("<</Font");
							bw.WriteLine("<<");
							foreach (KeyValuePair<string, int> pair in listFonts)
							{
								bw.WriteLine("/" + pair.Key + " " + pair.Value + " 0 R");
							}
							bw.WriteLine(">>");
							bw.WriteLine(">>");
							bw.WriteLine("endobj");
						}
						nObjIndex++;
					}
				}

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

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

					//全ページのインデックスを出力
					int nPagesReferenceIndex = nObjIndex;
					{
						listPage.Add(nObjIndex + 1);				//1ページ目のインデックスを渡す
						listPage.Add(nObjIndex + 3);				//2ページ目のインデックスを渡す
						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, @"abcあいうえお漢字123"));
							listTexts.Add(new PDFText("F1", 40, 50, 590, @"abAAA23 timesItalic"));
							listTexts.Add(new PDFText("F2", 40, 50, 640, @"abAAA2てすと3 "));


							//コンテンツの書き出し
							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);
							}
						}
					}

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

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

							listTexts.Add(new PDFText("F0", 40, 50, 540, @"ぺーじ2.abcあいうえお3"));
							listTexts.Add(new PDFText("F1", 40, 50, 590, @"page2 abAAA23 timesItalic"));
							listTexts.Add(new PDFText("F2", 40, 50, 640, @"abAAA2てすと3 "));


							//コンテンツの書き出し
							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>
		/// 欧文フォントの指定
		/// 
		/// デフォルトで用意されているフォントを利用する場合はこれで指定、
		/// 使えるフォント名(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);
		}



		/// <summary>
		/// 日本語フォントの指定(フォント埋め込みなし)
		/// 
		/// フォント名は↓
		/// https://www.adobe.com/jp/support/type/aj1-6.html
		/// 小塚明朝 Std Rは「KozMinStd-Regular」
		/// 小塚明朝 Pro Rは「KozMinPro-Regular」
		/// 小塚明朝 Pr6N Rは「KozMinPr6N-Regular」
		/// りょう Text PlusN Rは「RyoTextPlusN-Regular」
		/// </summary>
		void WriteFont_UnicodeJapanese(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 /Type0");
			bw.WriteLine("/Encoding /UniJIS-UTF16-H");			//ユニコード(big endian)
			bw.WriteLine("/DescendantFonts [" + (nObjIndex + 1) + " 0 R]");
			bw.WriteLine(">>");
			//bw.WriteLine(">>");
			//bw.WriteLine(">>");
			bw.WriteLine("endobj");

			nObjIndex++;

			_listnXref.Add(bw.BaseStream.Position);

			bw.WriteLine("" + nObjIndex + " 0 obj");
			bw.WriteLine("<</Type /Font");
			bw.WriteLine("/Subtype /CIDFontType0");
			bw.WriteLine("/BaseFont /" + strFont);
			bw.WriteLine("/CIDSystemInfo <<");
			bw.WriteLine("/Registry (Adobe)");
			//bw.WriteLine("/Ordering (UCS)");
			bw.WriteLine("/Ordering (Japan1)");
			bw.WriteLine("/Supplement 6");
			bw.WriteLine(">>");
			bw.WriteLine("/FontDescriptor " + (nObjIndex + 1) + " 0 R");
			bw.WriteLine(">>");
			bw.WriteLine("endobj");

			nObjIndex++;

			_listnXref.Add(bw.BaseStream.Position);

			bw.WriteLine("" + nObjIndex + " 0 obj");
			bw.WriteLine("<<");
			bw.WriteLine("/Type /FontDescriptor");
			bw.WriteLine("/FontName /" + strFont);
			bw.WriteLine("/Flags 4");
			//bw.WriteLine("/FontBBox [-437 -340 1147 1317]");
			bw.WriteLine("/FontBBox [0 0 0 0]");			//↓この辺どう取得したらいいのか不明
			bw.WriteLine("/ItalicAngle 0");
			bw.WriteLine("/Ascent 1317");
			bw.WriteLine("/Descent -349");
			bw.WriteLine("/CapHeight 742");
			bw.WriteLine("/StemV 80");
			bw.WriteLine(">>");
			bw.WriteLine("endobj");

			nObjIndex++;


			//フォント情報の保存
			_mapFontType.Add(strFontObjName, FONT_TYPE.ADOBE_JPN1_6);
		}
		/// <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;
					}

					IDictionary<ushort, byte[]> cmap = null;

					if (type == FONT_TYPE.EMBEDDED)
					{
						cmap = _cmapFonts[text.strFont];
						if (cmap == null)
						{
							//cmap取得失敗
							Debug.Assert(false);
							continue;
						}
					}


					string strText = text.strText;

					byte[] raw2 = null;

					{
						byte[] tmp = null;

						//フォントに応じてエンコーディング
						if (type == FONT_TYPE.ASCII)
							tmp = Encoding.ASCII.GetBytes(strText);
						else if (type == FONT_TYPE.ADOBE_JPN1_6)
							tmp = Encoding.BigEndianUnicode.GetBytes(strText);
						else if (type == FONT_TYPE.EMBEDDED)
							tmp = ConvertStringByCMap(strText, cmap);
						else
						{
							Debug.Assert(false);
						}

						// 「()\」をエスケープする
						using (MemoryStream ms_esc = new MemoryStream())
						{
							foreach (byte cb in tmp)
							{
								if (cb == 0x28		//'('
									|| cb == 0x29	//')'
									|| cb == 0x5c)	//'\'
								{
									ms_esc.WriteByte(0x5c);		//'\'
								}
								ms_esc.WriteByte(cb);
							}
							ms_esc.Flush();

							raw2 = ms_esc.ToArray();
						}
					}


					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);
			}
		}

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



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