找回密码
 立即注册
楼主: tonyhsie

ListAssFonts: 小工具,分析字幕使用的字型 (2023/06/13 更新)

  • TA的每日心情
    奋斗
    15 小时前
  • 签到天数: 857 天

    [LV.10]以坛为家III

    14

    主题

    355

    回帖

    9144

    VC币

    星辰大海

    Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

    积分
    864947
    tmdtmdtmdqq 发表于 2023-7-28 21:47:53 | 显示全部楼层
    本帖最后由 tmdtmdtmdqq 于 2023-7-28 21:49 编辑

    发现很多Fontworks的字体(遇到过好几次了),我在字幕中用Full font name/Postscript name的值做字体名,ListAssFonts并不认。而FontLoaderSub是可以识别且正常加载的。






    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    郁闷
    2016-12-31 01:33
  • 签到天数: 6 天

    [LV.2]偶尔看看I

    69

    主题

    1326

    回帖

    1万

    VC币

    星辰大海

    Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

    积分
    2854390

    卓越贡献

    tonyhsie  楼主| 发表于 2023-7-28 22:43:36 | 显示全部楼层
    tmdtmdtmdqq 发表于 2023-7-28 21:47
    发现很多Fontworks的字体(遇到过好几次了),我在字幕中用Full font name/Postscript name的值做字体名,L ...


    目前只有思源跟 Noto 的字型,ListAssFonts 有支援它們的 PostScript 名稱


    其它字型沒有,因為

    1. .Net Framework 無法獲取字型的 PostScript 名稱

    2. 不是每一個字體的 PostScript 名稱都可以用在字幕檔裡面,有的可以,有的不行

     思源 Noto 因為其高度使用率,所以我才另外 workaround 想辦法支援,其它字型可能無法一一比照辦理


    点评

    了解!  发表于 2023-7-28 23:44
    回复 支持 0 反对 1

    使用道具 举报

  • TA的每日心情
    奋斗
    15 小时前
  • 签到天数: 857 天

    [LV.10]以坛为家III

    14

    主题

    355

    回帖

    9144

    VC币

    星辰大海

    Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

    积分
    864947
    tmdtmdtmdqq 发表于 2023-7-29 08:11:05 | 显示全部楼层
    本帖最后由 tmdtmdtmdqq 于 2023-7-29 08:17 编辑
    tonyhsie 发表于 2023-7-28 22:43
    目前只有思源跟 Noto 的字型,ListAssFonts 有支援它們的 PostScript 名稱

    叫GPT研究了一下,发现.net可以调sharpfont库的接口来获取Postscript name

    然而sharpfont库是对freetype库做的一个上层封装,使用时候需要再引用freetype库的。
    如果只单单是获取Postscript name的话,引两个库有点累赘。
    所以不如直接调freetype库。freetype库的API文档在这里


    GPT写了个例子,我随手改了改。没.net开发环境,只能用特殊的方法测试了一下。 freetype库的DLL在这里下。
    不知道对你有没有用。贴一下:

    1. using System;
    2. using System.Runtime.InteropServices;
    3. using Library = System.IntPtr;
    4. using Face = System.IntPtr;
    5. using Encoding = System.UInt32;
    6. using Error = System.Int32;

    7. class Program
    8. {
    9.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    10.     public static extern Error FT_Init_FreeType(out Library library);

    11.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    12.     public static extern Error FT_New_Face(Library library, string filepath, uint faceIndex, out Face face);

    13.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    14.     public static extern string FT_Get_Postscript_Name(Face face);

    15.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    16.     private static extern Error FT_Select_Charmap(Face face, Encoding encoding);

    17.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    18.     internal static extern uint FT_Get_Sfnt_Name_Count(Face face);

    19.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    20.     internal static extern Error FT_Get_Sfnt_Name(Face face, uint nameIndex, out SfntNameRec aname);

    21.     //[DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    22.     //private static extern Error FT_Done_Face(Face face);

    23.     [DllImport("freetype.dll", CallingConvention = CallingConvention.Cdecl)]
    24.     // Destroy a given FreeType library object and all of its children, including resources, drivers, faces, sizes, etc.
    25.     private static extern Error FT_Done_FreeType(Library library);

    26.     [DllImport("kernel32.dll")]
    27.     public static extern bool FreeLibrary(Library hModule);

    28.     // define from https://freetype.sourceforge.net/freetype2/docs/reference/ft2-base_interface.html#FT_ENC_TAG
    29.     private static uint EncodingCalc(string s)
    30.     {
    31.         return ((uint)s[0] << 24) | ((uint)s[1] << 16) | ((uint)s[2] << 8) | (uint)s[3];
    32.     }

    33.     // define from https://freetype.sourceforge.net/freetype2/docs/reference/ft2-base_interface.html#FT_Encoding
    34.     static Encoding GetEncodingByName(string encodingName)
    35.     {
    36.         switch (encodingName.ToUpper())
    37.         {
    38.             case "NONE": return 0;
    39.             case "MS_SYMBOL": return EncodingCalc("symb");
    40.             case "UNICODE": return EncodingCalc("unic");
    41.             case "MS_SJIS":
    42.             case "SJIS": return EncodingCalc("sjis");
    43.             case "MS_GB2312":
    44.             case "GB2312": return EncodingCalc("gb  ");
    45.             case "MS_BIG5":
    46.             case "BIG5": return EncodingCalc("big5");
    47.             case "MS_WANSUNG":
    48.             case "WANSUNG": return EncodingCalc("wans");
    49.             case "MS_JOHAB":
    50.             case "JOHAB": return EncodingCalc("joha");
    51.             case "ADOBE_STANDARD": return EncodingCalc("ADOB");
    52.             case "ADOBE_EXPERT": return EncodingCalc("ADBE");
    53.             case "ADOBE_CUSTOM": return EncodingCalc("ADBC");
    54.             case "ADOBE_LATIN_1": return EncodingCalc("lat1");
    55.             case "OLD_LATIN_2": return EncodingCalc("lat2");
    56.             case "APPLE_ROMAN": return EncodingCalc("armn");
    57.             default: return 0;
    58.         }
    59.     }

    60.     static void Main()
    61.     {
    62.         // 动态加载Freetype DLL
    63.         Library library;
    64.         Error error = FT_Init_FreeType(out library);
    65.         if (error != 0)
    66.         {
    67.             Console.WriteLine("Failed to initialize Freetype library");
    68.             return;
    69.         }

    70.         // 打开字体文件
    71.         string fontPath = "z:\\FOT-TelopMinProN-D.otf";
    72.         Face face;
    73.         error = FT_New_Face(library, fontPath, 0, out face);
    74.         if (error != 0)
    75.         {
    76.             Console.WriteLine("Failed to open font file");
    77.             return;
    78.         }

    79.         // 选择Unicode编码
    80.         error = FT_Select_Charmap(face, GetEncodingByName("UNICODE"));
    81.         if (error != 0)
    82.         {
    83.             Console.WriteLine("Fail to select UNICODE charmap");
    84.             return;
    85.         }

    86.         // 获取字体文件的Postscript name字段
    87.         string scriptName = FT_Get_Postscript_Name(face);
    88.         Console.WriteLine("Postscript name: {0}", scriptName);

    89.         // 获取字体文件的全部字段(完整)
    90.         uint sfntNameCount = FT_Get_Sfnt_Name_Count(face);
    91.         for (uint i = 0; i < sfntNameCount; i++)
    92.         {
    93.             SfntNameRec rec = new SfntNameRec();
    94.             error = FT_Get_Sfnt_Name(face, i, out rec);
    95.             if (error == 0)
    96.             {
    97.                 SfntName sfntName = new SfntName(rec);
    98.                 // 各类型ID对应字段的含义可以参考: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
    99.                 Console.WriteLine("field value[{0}] - LanguageId: {1}    NameId: {2}    Value: {3}", i, sfntName.LanguageId, sfntName.NameId, sfntName.StringAnsi);
    100.             }
    101.         }
    102.         
    103.         // 释放资源
    104.         //FT_Done_Face(face);
    105.         FT_Done_FreeType(library);

    106.         // 卸载Freetype DLL
    107.         FreeLibrary(library);
    108.     }

    109.     // copy from https://github.com/Robmaister/SharpFont/blob/master/Source/SharpFont/TrueType/Internal/SfntNameRec.cs
    110.     internal struct SfntNameRec
    111.     {
    112.         internal ushort platform_id;
    113.         internal ushort encoding_id;
    114.         internal ushort language_id;
    115.         internal ushort name_id;

    116.         internal IntPtr @string;
    117.         internal uint string_len;
    118.     }

    119.     // copy from https://github.com/Robmaister/SharpFont/blob/master/Source/SharpFont/TrueType/SfntName.cs
    120.     class SfntName
    121.     {
    122.         private SfntNameRec rec;
    123.         internal SfntName(SfntNameRec rec)
    124.         {
    125.             this.rec = rec;
    126.         }

    127.         public ushort PlatformId
    128.         {
    129.             get
    130.             {
    131.                 return rec.platform_id;
    132.             }
    133.         }

    134.         public ushort EncodingId
    135.         {
    136.             get
    137.             {
    138.                 return rec.encoding_id;
    139.             }
    140.         }

    141.         public ushort LanguageId
    142.         {
    143.             get
    144.             {
    145.                 return rec.language_id;
    146.             }
    147.         }

    148.         public ushort NameId
    149.         {
    150.             get
    151.             {
    152.                 return rec.name_id;
    153.             }
    154.         }

    155.         public string String
    156.         {
    157.             get
    158.             {
    159.                 //TODO it may be possible to consolidate all of these properties
    160.                 //if the strings follow some sane structure. Otherwise, leave
    161.                 //them or add more overloads for common encodings like UTF-8.
    162.                 return Marshal.PtrToStringUni(rec.@string, (int) Math.Ceiling(rec.string_len/2.0));
    163.             }
    164.         }

    165.         public string StringAnsi
    166.         {
    167.             get
    168.             {
    169.                 return Marshal.PtrToStringAnsi(rec.@string, (int)rec.string_len);
    170.             }
    171.         }

    172.         public IntPtr StringPtr
    173.         {
    174.             get
    175.             {
    176.                 return rec.@string;
    177.             }
    178.         }
    179.     }
    180. }

    复制代码




    点评

    謝謝你,但目前不太可能為了PostScript name而讓ListAssFonts額外捆綁一個dll  发表于 2023-7-29 10:08
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    15 小时前
  • 签到天数: 857 天

    [LV.10]以坛为家III

    14

    主题

    355

    回帖

    9144

    VC币

    星辰大海

    Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

    积分
    864947
    tmdtmdtmdqq 发表于 2023-7-29 19:12:07 | 显示全部楼层
    调教了一个下午GPT,终于调教出来了。.net原生方法获取Postscript name。支持TTF/OTF/TTC格式。

    CFF比较复杂,建议不处理,只判断tag="name"已经足够。
    此处也把CFF的判断代码贴一下,虽然用false给短路了,但在某种情况下可能也会有参考价值。

    贴代码:
    1. using System;
    2. using System.IO;

    3. namespace TTFParser
    4. {
    5.     class Program
    6.     {
    7.         static void Main(string[] args)
    8.         {
    9.             Console.Write("Input font file path:");
    10.             string filePath = Console.ReadLine();

    11.             // Read the file as binary data
    12.             byte[] fileData = File.ReadAllBytes(filePath);

    13.             if (fileData.Length >= 4 && fileData[0] == 0x74 && fileData[1] == 0x74 && fileData[2] == 0x63 && fileData[3] == 0x66)
    14.             {
    15.                 // TTC file signature: 0x74 0x74 0x63 0x66 (ttcf)

    16.                 // Parse the TTC header
    17.                 int numFonts = ReadUInt32(fileData, 8);
    18.                 int offsetTableOffset = 12;

    19.                 for (int fontIndex = 0; fontIndex < numFonts; fontIndex++)
    20.                 {
    21.                     // Get the offset and length of the specified font
    22.                     int fontOffset = ReadUInt32(fileData, offsetTableOffset + (fontIndex * 4));

    23.                     FontAttributeAnalysis(fileData, fontOffset);
    24.                 }
    25.             }
    26.             else
    27.             {
    28.                 // OTF file signature: 0x4F 0x54 0x54 0x4F (OTTO)
    29.                 // TTF file signature: 0x00 0x01 0x00 0x00
    30.                 FontAttributeAnalysis(fileData, 0);
    31.             }
    32.         }

    33.         static void FontAttributeAnalysis(byte[] fileData, int tableInitOffset)
    34.         {
    35.             // Parse the font header
    36.             int tableCount = ReadUInt16(fileData, tableInitOffset + 4);
    37.             int tableOffset = tableInitOffset + 12;

    38.             string familyName = null;
    39.             string postscriptName = null;

    40.             // Iterate through the tables to find the name table
    41.             for (int i = 0; i < tableCount; i++)
    42.             {
    43.                 int tagOffset = tableOffset + (i * 16);
    44.                 string tag = ReadString(fileData, tagOffset, 4);

    45.                 if (tag == "name")
    46.                 {
    47.                     int nameTableOffset = ReadUInt32(fileData, tagOffset + 8);
    48.                     int nameCount = ReadUInt16(fileData, nameTableOffset + 2);
    49.                     int stringOffset = ReadUInt16(fileData, nameTableOffset + 4);

    50.                     // Iterate through the name records to find family name and postscript name
    51.                     for (int j = 0; j < nameCount; j++)
    52.                     {
    53.                         int nameRecordOffset = nameTableOffset + 6 + (j * 12);

    54.                         int platformID = ReadUInt16(fileData, nameRecordOffset);
    55.                         int encodingID = ReadUInt16(fileData, nameRecordOffset + 2);
    56.                         int languageID = ReadUInt16(fileData, nameRecordOffset + 4);
    57.                         int nameID = ReadUInt16(fileData, nameRecordOffset + 6);
    58.                         int nameLength = ReadUInt16(fileData, nameRecordOffset + 8);
    59.                         int nameOffset = nameTableOffset + stringOffset + ReadUInt16(fileData, nameRecordOffset + 10);

    60.                         // 各类型ID对应字段的含义可以参考: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
    61.                         Console.WriteLine("[DEBUG] platformID={0}    encodingID={1}    languageID={2}    nameID={3}    value={4}",
    62.                             platformID, encodingID, languageID, nameID, ReadString(fileData, nameOffset, nameLength));

    63.                         if (nameID == 1) // Family name ID
    64.                         {
    65.                             if (familyName == null)
    66.                             {
    67.                                 familyName = ReadString(fileData, nameOffset, nameLength);
    68.                             }
    69.                         }
    70.                         else if (nameID == 6) // Postscript name ID
    71.                         {
    72.                             if (postscriptName == null)
    73.                             {
    74.                                 postscriptName = ReadString(fileData, nameOffset, nameLength);
    75.                             }
    76.                         }

    77.                         //if (familyName != null && postscriptName != null)
    78.                         //{
    79.                         //    break;
    80.                         //}
    81.                     }
    82.                     break;
    83.                 }
    84.                 else if (tag == "CFF " && false)  //先用false屏蔽,建议不处理
    85.                 {
    86.                     // CFF比较复杂,建议不处理,只判断tag="name"已经足够。这只是简单的例子,没有全覆盖,上面tag判断的字符串有个空格,要注意
    87.                     // Adobe的CFF规范 https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf
    88.                     // Parse the 'CFF ' table to find the Postscript name.
    89.                     // The CFF table format is complex and not covered in this simple example.
    90.                     // You need to understand the CFF specification to implement this function.
    91.                     // Here is a very simplified example:

    92.                     Console.WriteLine("[DEBUG] CCF running...");
    93.                     int cffTableOffset = ReadUInt32(fileData, tagOffset + 8);
    94.                     // The Name INDEX is located at offset 2 in the CFF table.
    95.                     int nameIndexOffset = cffTableOffset + 2;
    96.                     int nameCount = ReadUInt16(fileData, nameIndexOffset);

    97.                     // The Top DICT INDEX follows the Name INDEX.
    98.                     int dictIndexOffset = nameIndexOffset + 2 + nameCount * 2 + 1;

    99.                     for (int j = 0; j < nameCount; j++)
    100.                     {
    101.                         // Parse the Name INDEX to get the font name.
    102.                         int nameOffset = ReadUInt16(fileData, nameIndexOffset + 2 + j * 2);
    103.                         int nextNameOffset = ReadUInt16(fileData, nameIndexOffset + 2 + (j + 1) * 2);
    104.                         if (cffTableOffset + nameOffset < nextNameOffset - nameOffset)
    105.                         {
    106.                             string fontName = ReadString(fileData, cffTableOffset + nameOffset, nextNameOffset - nameOffset);
    107.                             Console.WriteLine("CCF Font Name: " + fontName);
    108.                         }

    109.                         // Parse the Top DICT INDEX to get the font dictionary.
    110.                         int dictOffset = ReadUInt16(fileData, dictIndexOffset + 2 + j * 2);
    111.                         int nextDictOffset = ReadUInt16(fileData, dictIndexOffset + 2 + (j + 1) * 2);

    112.                         
    113.                         for (int k = dictOffset; k < nextDictOffset; k++)
    114.                         {
    115.                             // The PostScript name is stored in the font dictionary with the operator 12 30.
    116.                             if (fileData[cffTableOffset + k] == 12 && fileData[cffTableOffset + k + 1] == 30)
    117.                             {
    118.                                 // The PostScript name follows the operator and is encoded as a string.
    119.                                 int postscriptNameLength = fileData[cffTableOffset + k + 2];
    120.                                 postscriptName = ReadString(fileData, cffTableOffset + k + 3, postscriptNameLength);
    121.                                 break;
    122.                             }

    123.                             // The Family name is stored in the font dictionary with the operator 12 38.
    124.                             if (fileData[cffTableOffset + k] == 12 && fileData[cffTableOffset + k + 1] == 38)
    125.                             {
    126.                                 // The Family name follows the operator and is encoded as a string.
    127.                                 int familyNameLength = fileData[cffTableOffset + k + 2];
    128.                                 familyName = ReadString(fileData, cffTableOffset + k + 3, familyNameLength);
    129.                                 break;
    130.                             }
    131.                         }
    132.                     }
    133.                 }
    134.             }

    135.             Console.WriteLine("Family Name: " + familyName);
    136.             Console.WriteLine("Postscript Name: " + postscriptName);
    137.             Console.WriteLine("=============================");
    138.         }

    139.         static int ReadUInt16(byte[] data, int offset)
    140.         {
    141.             return (data[offset] << 8) | data[offset + 1];
    142.         }

    143.         static int ReadUInt32(byte[] data, int offset)
    144.         {
    145.             return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
    146.         }

    147.         static string ReadString(byte[] data, int offset, int length)
    148.         {
    149.             return System.Text.Encoding.ASCII.GetString(data, offset, length);
    150.         }
    151.     }
    152. }
    复制代码






    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    郁闷
    2016-12-31 01:33
  • 签到天数: 6 天

    [LV.2]偶尔看看I

    69

    主题

    1326

    回帖

    1万

    VC币

    星辰大海

    Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

    积分
    2854390

    卓越贡献

    tonyhsie  楼主| 发表于 2023-7-29 20:08:00 | 显示全部楼层
    tmdtmdtmdqq 发表于 2023-7-29 19:12
    调教了一个下午GPT,终于调教出来了。.net原生方法获取Postscript name。支持TTF/OTF/TTC格式。

    CFF比较 ...




    很多年前就寫過 ttf parser 了,三年前徹底拿掉這些 code
    因為有更好的方式來處理字型檔案,不需要自己動手


    btw,我覺得你的研究精神很好,花點時間,你很快就可以寫出一個自己的字型工具
    就像我當年寫出 ListAssFonts 一樣

    (認真的,不是嘲諷)


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2024-4-20 14:56
  • 签到天数: 146 天

    [LV.7]常住居民III

    23

    主题

    625

    回帖

    3097

    VC币

    星辰大海

    Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20

    积分
    402442
    sommio 发表于 2023-7-31 03:43:57 | 显示全部楼层
    tmdtmdtmdqq 发表于 2023-7-28 21:47
    发现很多Fontworks的字体(遇到过好几次了),我在字幕中用Full font name/Postscript name的值做字体名,L ...

    我觉得用「Weight-stretch-style font family model」比较安全,因为是主流的
    无论是 Office 还是 aegisub 的字体选择器都使用这个 model,既使用 family

    像之前我发帖报错的「思源黑体 Bold (v2.000+)」,实际上官方 OTF 包含这个 Full Name,但 mpv+fontconfig 无法调用它
    在此前的版本中它作为 family 存在

    回复 支持 反对

    使用道具 举报

    该用户从未签到

    0

    主题

    2

    回帖

    0

    VC币

    新手上路

    Rank: 1

    积分
    20
    Qianshijiang 发表于 2023-8-24 03:56:35 | 显示全部楼层
    我的神!
    回复

    使用道具 举报

  • TA的每日心情
    开心
    14 小时前
  • 签到天数: 227 天

    [LV.7]常住居民III

    2

    主题

    15

    回帖

    0

    VC币

    高级会员

    Rank: 4

    积分
    26851
    wzdc 发表于 2023-9-9 16:37:04 | 显示全部楼层
    本帖最后由 wzdc 于 2023-9-9 16:40 编辑

    az

    点评

    繼續  发表于 2023-9-10 01:31
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    郁闷
    2024-4-13 00:30
  • 签到天数: 5 天

    [LV.2]偶尔看看I

    1

    主题

    1

    回帖

    0

    VC币

    新手上路

    Rank: 1

    积分
    474
    sdyt6383985 发表于 2023-9-11 23:08:20 | 显示全部楼层
    哇!发现好东西!感谢楼主分享!
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    0

    主题

    1

    回帖

    0

    VC币

    新手上路

    Rank: 1

    积分
    17
    onezhai 发表于 2023-9-19 19:51:34 | 显示全部楼层
    好感动,这么久了还在更新软件。感恩作者!
    回复 支持 1 反对 0

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    快速回复 返回顶部 返回列表