前回作成したPDFでは、文字を選択してクリップボードにコピー、そしてメモ帳にペーストしようとすると文字化けします。
これはユニコードへの「変換表」をPDF内に埋め込んでいないためです。
今回はこの変換表の埋め込み処理を追加します。
と言っても、オブジェクトを1つ用意するだけです。
/// <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]); }