今回は前回取得したopen type fontに関する情報を利用してPDFファイルにフォントを埋め込みます。
まず、前回の方法で変換表だけを抜き出したテキストファイル「cmap_msgothic.txt」を作成します。
Windows7に含まれていたMSゴシックでは1万5747行になりました。
1. Char 0000 -> Index 1 2. Char 000D -> Index 2 3. Char 0020 -> Index 3 Char 0021 -> Index 4 Char 0022 -> Index 5 Char 0023 -> Index 6 Char 0024 -> Index 7 Char 0025 -> Index 8 Char 0026 -> Index 9 Char 0027 -> Index 10 Char 0028 -> Index 11 Char 0029 -> Index 12 Char 002A -> Index 13 Char FFE6 -> Index 15740 4357. Char FFE8 -> Index 15741 Char FFE9 -> Index 15742 Char FFEA -> Index 15743 Char FFEB -> Index 15744 Char FFEC -> Index 15745 Char FFED -> Index 15746 Char FFEE -> Index 15747
作成したcmap_msgothic.txtと、フォントファイルであるmsgothic.otfの2ファイルを、プロジェクトの実行フォルダ(debugフォルダ)へコピーしておきます。
そしてフォント埋め込みのコード作成。
本来であれば、PDF内で使われている文字分だけのフォントを埋め込むべきですが、
それをするのが面倒なため、msgothic.otfを丸ごとオブジェクトとして埋め込みました。
そして表示する文章はcmap_msgothic.txtを参照して、ユニコードからインデックス値に変換して書き出しています。
これでフォントを埋め込んだPDFが作成できました。
PDF内で文字を選択してクリップボードにコピー、そしてメモ帳にペーストしようとすると文字化けします。
これはインデックス値からユニコードへと変換する表を埋め込んでいないためです。これは次回以降へ持ち越し。
また、今は変換表をcmap_msgothic.txtという形で手動で用意しています。
これをフォントファイルから直接読み取る処理も次回以降です。
using System; //追加 using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Text; using System.Text.RegularExpressions; //追加 namespace Pdf2Pdf { class PDFTextWriter { enum FONT_TYPE { ASCII, ADOBE_JPN1_6, EMBEDDED } //クロスリファレンス生成用データ(各オブジェクトの位置を保存) 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"); //欧文フォント指定 //WriteFont_UnicodeJapanese(bw, ref nObjIndex, "KozMinPr6N-Regular", "F0"); //日本語フォント指定(フォント埋め込みなし) //フォント埋め込み { 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, "F0", "MS-Gothic", "MS Gothic", @"msgothic.otf"); } //埋め込みフォント一覧のみのリソース 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ページ目のインデックスを渡す 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")); //コンテンツの書き出し 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; }
//フォントごとのcmap変換表を保管 IDictionary<string, IDictionary<ushort, byte[]>> _cmapFonts = new Dictionary<string, IDictionary<ushort, byte[]>>(); /// <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++; //フォント情報の保存 _mapFontType.Add(strFontObjName, FONT_TYPE.EMBEDDED); } 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; } IDictionary<ushort, byte[]> 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); 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); } } /// <summary> /// 文字列をcmapにもとづいて変換する /// </summary> byte[] ConvertStringByCMap(string strText, IDictionary<ushort, byte[]> cmap) { byte[] data; using (MemoryStream ms = new MemoryStream()) using (BinaryWriter bw = new BinaryWriter(ms)) { foreach (ushort c in strText) { bool bFind = false; byte[] value; bFind = cmap.TryGetValue(c, out value); if (bFind) bw.Write(value); //文字が見つからなかったら「.」を代わりに出力 if (bFind == false) { bFind = cmap.TryGetValue('.', out value); if (bFind) bw.Write(value); } } bw.Flush(); return ms.ToArray(); } }