今回はopen type fontのフォントファイルからフォント名/フォントファミリー名を取得します。
open typeのファイル仕様に沿ってファイルを読むのみです。
フォント名はプラットフォームや表示言語に応じて複数格納されています。
今回はWindowsのユニコード向け、英語圏用の名前を取得/利用しています。
/// <summary> /// フォントの埋め込み /// /// フォント名/フォントファミリー名を指定しない場合はフォントファイルから自動取得を試みる /// </summary> void WriteFont_EmbeddedUnicode(PDFRawWriter bw, ref int nObjIndex, string strFontObjName, string strFontFile, string strFont = "", string strFontFamily = "") { ushort nRangeMin = 0xFFFF; ushort nRangeMax = 0; //opentypeフォントファイルからcmapを読み込む IDictionary<ushort, byte[]> cmap = LoadCMap(strFontFile, out nRangeMin, out nRangeMax, ref strFontFamily, ref strFont); _cmapFonts.Add(strFontObjName, cmap); _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++; ////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
/// <summary> /// open type fontファイルからcmapを読み取る /// /// open type fontの仕様通りにファイルを読むだけ /// マジックナンバーなどでファイルチェックをするべきだがしていない /// /// strFontFamilyName/strFontPostScriptName は==""だった場合のみ、フォントファイルから読み出す /// /// 仕様 /// https://www.microsoft.com/typography/otspec/otff.htm /// </summary> IDictionary<ushort, byte[]> LoadCMap(string strFontFile, out ushort nRangeMin, out ushort nRangeMax, ref string strFontFamilyName, ref string strFontPostScriptName)//, out int nBBXMin, out int nBBXMax, out int nBBYMin, out int nBBYMax, out int nAscender, out int nDescender) { IDictionary<ushort, byte[]> cmap = new Dictionary<ushort, byte[]>(); nRangeMin = 0xFFFF; nRangeMax = 0; int nBBXMin = 0; int nBBXMax = 0; int nBBYMin = 0; int nBBYMax = 0; int nAscender = 0; int nDescender = 0; using (FileStream fs = new FileStream(strFontFile, FileMode.Open, FileAccess.Read)) using (BinaryReader br = new BinaryReader(fs)) { // https://www.microsoft.com/typography/otspec/otff.htm byte[] sfntVer = br.ReadBytes(4); uint nTableCount = ByteToUInt_BE(br.ReadBytes(2)); uint nSearchRange = ByteToUInt_BE(br.ReadBytes(2)); uint nEntrySelector = ByteToUInt_BE(br.ReadBytes(2)); uint nRangeShift = ByteToUInt_BE(br.ReadBytes(2)); uint nCMapOffset = 0; uint nCMapLength = 0; uint nHeadOffset = 0; uint nHeadLength = 0; uint nHheaOffset = 0; uint nHheaLength = 0; uint nOS2Offset = 0; uint nOS2Length = 0; uint nNameOffset = 0; uint nNameLength = 0; for (uint i = 0; i < nTableCount; i++) { byte[] tag = br.ReadBytes(4); uint checkSum = ByteToUInt_BE(br.ReadBytes(4)); uint offset = ByteToUInt_BE(br.ReadBytes(4)); // Offset from beginning of TrueType font file. uint length = ByteToUInt_BE(br.ReadBytes(4)); string strTag = Encoding.ASCII.GetString(tag); if (strTag == "cmap") { nCMapOffset = offset; nCMapLength = length; } if (strTag == "head") { nHeadOffset = offset; nHeadLength = length; } if (strTag == "hhea") { nHheaOffset = offset; nHheaLength = length; } if (strTag == "OS/2") { nOS2Offset = offset; nOS2Length = length; } if (strTag == "name") { nNameOffset = offset; nNameLength = length; } } if (strFontFamilyName == "" || strFontPostScriptName == "") { if (nNameOffset > 0 && nNameLength > 0) { fs.Seek(nNameOffset, SeekOrigin.Begin); // https://www.microsoft.com/typography/otspec/name.htm uint format = ByteToUInt_BE(br.ReadBytes(2)); uint count = ByteToUInt_BE(br.ReadBytes(2)); //Number of name records. uint stringOffset = ByteToUInt_BE(br.ReadBytes(2)); //Offset to start of string storage (from start of table). //プラットフォーム3、エンコーディング1(Windowsのunicode)で、さらにランゲージID1033(英語圏用の名前)のデータのみ収集 List<uint> list31NameID = new List<uint>(); List<uint> list31Length = new List<uint>(); List<uint> list31Offset = new List<uint>(); //Name Records for (uint i = 0; i < count; i++) { uint platformID = ByteToUInt_BE(br.ReadBytes(2)); uint encodingID = ByteToUInt_BE(br.ReadBytes(2)); uint languageID = ByteToUInt_BE(br.ReadBytes(2)); uint nameID = ByteToUInt_BE(br.ReadBytes(2)); uint length = ByteToUInt_BE(br.ReadBytes(2)); //String length (in bytes). uint offset = ByteToUInt_BE(br.ReadBytes(2)); //String offset from start of storage area (in bytes). //プラットフォーム3、エンコーディング1(Windowsのunicode)で、さらにランゲージID1033(英語圏用の名前)のデータのみ収集 if (platformID != 3 || encodingID != 1 || languageID != 1033) continue; list31NameID.Add(nameID); list31Length.Add(length); list31Offset.Add(offset); } //format==1の場合のみ言語タグ情報がある if (format == 1) { uint langTagCount = ByteToUInt_BE(br.ReadBytes(2)); //LangTagRecord for (uint i = 0; i < langTagCount; i++) { uint length = ByteToUInt_BE(br.ReadBytes(2)); uint offset = ByteToUInt_BE(br.ReadBytes(2)); // Language-tag string offset from start of storage area (in bytes). } } //Storage area開始 long nStorageStart = fs.Position; for (int i = 0; i < list31NameID.Count; i++) { if (list31NameID[i] == 1) //font family name { if (strFontFamilyName == "") { fs.Seek(nStorageStart + list31Offset[i], SeekOrigin.Begin); strFontFamilyName = Encoding.BigEndianUnicode.GetString(br.ReadBytes((int)list31Length[i])); } } if (list31NameID[i] == 6) //Postscript name for the font { if (strFontPostScriptName == "") { fs.Seek(nStorageStart + list31Offset[i], SeekOrigin.Begin); strFontPostScriptName = Encoding.BigEndianUnicode.GetString(br.ReadBytes((int)list31Length[i])); } } } } } if (nHheaOffset > 0 && nHheaLength > 0) { fs.Seek(nHheaOffset, SeekOrigin.Begin); // https://www.microsoft.com/typography/otspec/hhea.htm byte[] version = br.ReadBytes(4); nAscender = ByteToInt_BE(br.ReadBytes(2)); nDescender = ByteToInt_BE(br.ReadBytes(2)); int LineGap = ByteToInt_BE(br.ReadBytes(2)); uint advanceWidthMax = ByteToUInt_BE(br.ReadBytes(2)); int minLeftSideBearing = ByteToInt_BE(br.ReadBytes(2)); int minRightSideBearing = ByteToInt_BE(br.ReadBytes(2)); int xMaxExtent = ByteToInt_BE(br.ReadBytes(2)); int caretSlopeRise = ByteToInt_BE(br.ReadBytes(2)); int caretSlopeRun = ByteToInt_BE(br.ReadBytes(2)); int caretOffset = ByteToInt_BE(br.ReadBytes(2)); int reserved1 = ByteToInt_BE(br.ReadBytes(2)); int reserved2 = ByteToInt_BE(br.ReadBytes(2)); int reserved3 = ByteToInt_BE(br.ReadBytes(2)); int reserved4 = ByteToInt_BE(br.ReadBytes(2)); int metricDataFormat = ByteToInt_BE(br.ReadBytes(2)); uint numberOfHMetrics = ByteToUInt_BE(br.ReadBytes(2)); }
/// <summary> /// "小説家になろう"の縦書きPDF変換処理 /// </summary> public bool ConvertPDFFile(string strFile, PDFTextReader srcPDF) { 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, @"msgothic.otf");//, "MS-Gothic", "MS Gothic"); } //フォント一覧のみのリソース 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++; } }
/// <summary> /// PDF作成例 /// </summary> 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, @"msgothic.otf", "MS-Gothic", "MS Gothic"); } { 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); //日本語フォント指定(フォント埋め込みなし) }