第10回 PDF内の文字をコピペできるようにする

前回作成したPDFでは、文字を選択してクリップボードにコピー、そしてメモ帳にペーストしようとすると文字化けします。
これはユニコードへの「変換表」をPDF内に埋め込んでいないためです。

今回はこの変換表の埋め込み処理を追加します。
と言っても、オブジェクトを1つ用意するだけです。

■PDFTextWriter.cs
		/// <summary>
		/// フォントの埋め込み
		/// </summary>
		void WriteFont_EmbeddedUnicode(PDFRawWriter bw, ref int nObjIndex, string strFontObjName, string strFont, string strFontFamily, string strFontFile)
		{
			_listnXref.Add(bw.BaseStream.Position);
			{
				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Type /Font");
				bw.WriteLine("/BaseFont /" + strFont);
				bw.WriteLine("/Subtype /Type0");
				bw.WriteLine("/Encoding /Identity-H");			//PDF独自のエンコード
				bw.WriteLine("/DescendantFonts [" + (nObjIndex + 1) + " 0 R]");
				bw.WriteLine("/ToUnicode " + (nObjIndex + 4) + " 0 R");		//ToUnicode変換表
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;


			int nDescendantFontsObjIndex = 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("/CIDToGIDMap/Identity");
				bw.WriteLine("/CIDSystemInfo <<");
				bw.WriteLine("/Registry (Adobe)");
				bw.WriteLine("/Ordering (Identity)");		//Japan1にはしない
				bw.WriteLine("/Supplement 0");				//6にした方がいい?
				bw.WriteLine(">>");
				bw.WriteLine("/FontDescriptor " + (nObjIndex + 1) + " 0 R");
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;



			ushort nRangeMin = 0xFFFF;
			ushort nRangeMax = 0;



			//CMAPの準備
			//{
				string strFontCMapFile = @"cmap_msgothic.txt";

				//
				//CMAPの読み込み
				//
				//以下を実行してcmap.txtを取得、その中から「Char 30D6 -> Index 2121」というようなunicode用のcmapテーブルを抜き出してcmap_msgothic.txtに保存
				// ttfdump.exe HuiFont29.ttf -tcmap -nx >cmap.txt
				//
				// ttfdump.exeは以下からダウンロード(fonttools.exeに含まれる)
				// https://www.microsoft.com/typography/tools/tools.aspx
				// https://download.microsoft.com/download/f/f/a/ffae9ec6-3bf6-488a-843d-b96d552fd815/FontTools.exe
				//
				//
				//	//本当なら↓のコードで簡単にフォントからcmapを取得できるはずだけど、
				//	//きちんとした対応にならない???
				//	{
				//		//「PresentationCore」への参照追加
				//		GlyphTypeface gtf = new GlyphTypeface(new Uri(strFontFile));
				//		var cmap = gtf.CharacterToGlyphMap;
				//	}
				//

				IDictionary<ushort, byte[]> cmap = new Dictionary<ushort, byte[]>();
				_cmapFonts.Add(strFontObjName, cmap);

				using (FileStream fs = new FileStream(strFontCMapFile, FileMode.Open, FileAccess.Read))
				using (StreamReader sr = new StreamReader(fs))
				{
					string strCMap = sr.ReadToEnd();

					Regex re = new Regex(@"Char ([ABCDEFabcdef\d]+) -> Index (\d+)", RegexOptions.IgnoreCase);
					Match m = re.Match(strCMap);
					while (m.Success)
					{
						try
						{
							string strChar = m.Groups[1].Value;
							string strIndex = m.Groups[2].Value;

							ushort nChar = Convert.ToUInt16(strChar, 16);
							ushort nIndex = ushort.Parse(strIndex);

							//ビッグエンディアン変換
							byte tmp;
							byte[] bytes = BitConverter.GetBytes(nIndex);
							tmp = bytes[1];
							bytes[1] = bytes[0];
							bytes[0] = tmp;

							cmap.Add(nChar, bytes);

							//indexの最小値最大値を保存しておく
							if (nIndex < nRangeMin)
								nRangeMin = nIndex;
							if (nIndex > nRangeMax)
								nRangeMax = nIndex;
						}
						catch (Exception)
						{
						}

						m = m.NextMatch();
					}
				}
			//}



			int nFontDescriptorObjIndex = nObjIndex;
			_listnXref.Add(bw.BaseStream.Position);
			{
				bw.WriteLine("" + nObjIndex + " 0 obj");
				bw.WriteLine("<</Type /FontDescriptor");
				bw.WriteLine("/FontName /" + strFont);
				bw.WriteLine("/FontFamily(" + strFontFamily + ")");
				//bw.WriteLine(@"/Style<</Panose <0801020B0609070205080204> >>");		//The font family class and subclass ID bytes, given in the sFamilyClass field of the “OS/2” table in a TrueType font. This field is documented in Microsoft’s TrueType 1.0 Font Files Technical Specification

				//bw.WriteLine("/CIDSet 15 0 R");				//CID表
				bw.WriteLine("/FontFile2 " + (nObjIndex + 1) + " 0 R");
				bw.WriteLine("/Flags 6");									//Font uses the Adobe standard Latin character set or a subset of it
				bw.WriteLine("/FontBBox [0 0 0 0]");																		//だから0 0 0 0で自動にする
				//bw.WriteLine("/FontBBox [-437 -340 1147 1317]");															//指定例
				bw.WriteLine("/ItalicAngle 0");			//PostScriptHeaaderの値?面倒だからスルー
				//bw.WriteLine("/Lang/ja");				//日本語指定しておく?
				bw.WriteLine("/Ascent 1317");
				bw.WriteLine("/Descent -349");
				bw.WriteLine("/CapHeight 742");			//取得方法不明
				bw.WriteLine("/StemV 80");				//取得方法不明
				bw.WriteLine(">>");
				bw.WriteLine("endobj");
			}
			nObjIndex++;




			int nFontFileObjIndex = nObjIndex;
			_listnXref.Add(bw.BaseStream.Position);
			{
				long nEncodedLength;
				long nDecodedLength;
				byte[] data;

				using (FileStream fs = new FileStream(strFontFile, FileMode.Open, FileAccess.Read))
				using (BinaryReader br = new BinaryReader(fs))
				{
					nDecodedLength = fs.Length;
					data = br.ReadBytes((int)nDecodedLength);
				}

				byte[] compress = CompressDeflate(data);

				nEncodedLength = compress.Length;

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




			//ToUnicode変換表
			if (nRangeMin <= nRangeMax)
			{
				byte[] data;

				using (MemoryStream ms = new MemoryStream())
				using (StreamWriter bwms = new StreamWriter(ms, Encoding.ASCII))
				{
					//	/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <<
					//	/Registry (TT1+0) /Ordering (T42UV) /Supplement 0 >> def
					//	/CMapName /TT1+0 def
					//	/CMapType 2 def
					//	1 begincodespacerange <0003> <0836> endcodespacerange		//<index>の最小最大値
					//	6 beginbfchar				//続く行数
					//	<0003> <0020>				//<index> <unicode>
					//	<001d> <003A>
					//	<0044> <0061>
					//	<0057> <0074>
					//	<005b> <0078>
					//	<0836> <3042>
					//	endbfchar
					//	1 beginbfrange				//続く行数
					//	<0010> <001a> <002D>		//いっぱいあるとき用。<0010> <001a> が<002D>に対応
					//	endbfrange
					//	endcmap CMapName currentdict /CMap defineresource pop end end

					bwms.Write("/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <<\r");
					bwms.Write("/Registry (" + strFontObjName + "+0) /Ordering (T42UV) /Supplement 0 >> def\r");
					bwms.Write("/CMapName /" + strFontObjName + "+0 def\r");
					bwms.Write("/CMapType 2 def\r");
					bwms.Write("1 begincodespacerange <" + ConvertToHex_BE(nRangeMin) + "> <" + ConvertToHex_BE(nRangeMax) + "> endcodespacerange\r");

					bwms.Write("" + cmap.Count + " beginbfchar\r");
					foreach (KeyValuePair<ushort, byte[]> pair in cmap)
					{
						string value = String.Format("{0:X2}", pair.Value[0]) + String.Format("{0:X2}", pair.Value[1]);
						bwms.Write("<" + value + "> <" + ConvertToHex_BE(pair.Key) + ">\r");
					}
					bwms.Write("endbfchar\r");
					bwms.Write("endcmap CMapName currentdict /CMap defineresource pop end end\r");
					bwms.Flush();

					data = ms.ToArray();
				}

				if (data.Length > 0)
				{
					//17 0 obj
					//<</Filter/FlateDecode/Length 269>>stream
					_listnXref.Add(bw.BaseStream.Position);
					{
						long nEncodedLength;

						byte[] compress = CompressDeflate(data);

						nEncodedLength = compress.Length;

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

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


		string ConvertToHex_BE(ushort value)
		{
			byte[] bytes = BitConverter.GetBytes(value);

			return String.Format("{0:X2}", bytes[1]) + String.Format("{0:X2}", bytes[0]);
		}

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


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