tmdtmdtmdqq 发表于 2023-7-28 21:47:53

本帖最后由 tmdtmdtmdqq 于 2023-7-28 21:49 编辑

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






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 想辦法支援,其它字型可能無法一一比照辦理


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在这里下。
不知道对你有没有用。贴一下:

using System;
using System.Runtime.InteropServices;
using Library = System.IntPtr;
using Face = System.IntPtr;
using Encoding = System.UInt32;
using Error = System.Int32;

class Program
{
   
    public static extern Error FT_Init_FreeType(out Library library);

   
    public static extern Error FT_New_Face(Library library, string filepath, uint faceIndex, out Face face);

   
    public static extern string FT_Get_Postscript_Name(Face face);

   
    private static extern Error FT_Select_Charmap(Face face, Encoding encoding);

   
    internal static extern uint FT_Get_Sfnt_Name_Count(Face face);

   
    internal static extern Error FT_Get_Sfnt_Name(Face face, uint nameIndex, out SfntNameRec aname);

    //
    //private static extern Error FT_Done_Face(Face face);

   
    // Destroy a given FreeType library object and all of its children, including resources, drivers, faces, sizes, etc.
    private static extern Error FT_Done_FreeType(Library library);

   
    public static extern bool FreeLibrary(Library hModule);

    // define from https://freetype.sourceforge.net/freetype2/docs/reference/ft2-base_interface.html#FT_ENC_TAG
    private static uint EncodingCalc(string s)
    {
      return ((uint)s << 24) | ((uint)s << 16) | ((uint)s << 8) | (uint)s;
    }

    // define from https://freetype.sourceforge.net/freetype2/docs/reference/ft2-base_interface.html#FT_Encoding
    static Encoding GetEncodingByName(string encodingName)
    {
      switch (encodingName.ToUpper())
      {
            case "NONE": return 0;
            case "MS_SYMBOL": return EncodingCalc("symb");
            case "UNICODE": return EncodingCalc("unic");
            case "MS_SJIS":
            case "SJIS": return EncodingCalc("sjis");
            case "MS_GB2312":
            case "GB2312": return EncodingCalc("gb");
            case "MS_BIG5":
            case "BIG5": return EncodingCalc("big5");
            case "MS_WANSUNG":
            case "WANSUNG": return EncodingCalc("wans");
            case "MS_JOHAB":
            case "JOHAB": return EncodingCalc("joha");
            case "ADOBE_STANDARD": return EncodingCalc("ADOB");
            case "ADOBE_EXPERT": return EncodingCalc("ADBE");
            case "ADOBE_CUSTOM": return EncodingCalc("ADBC");
            case "ADOBE_LATIN_1": return EncodingCalc("lat1");
            case "OLD_LATIN_2": return EncodingCalc("lat2");
            case "APPLE_ROMAN": return EncodingCalc("armn");
            default: return 0;
      }
    }

    static void Main()
    {
      // 动态加载Freetype DLL
      Library library;
      Error error = FT_Init_FreeType(out library);
      if (error != 0)
      {
            Console.WriteLine("Failed to initialize Freetype library");
            return;
      }

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

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

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

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

      // 卸载Freetype DLL
      FreeLibrary(library);
    }

    // copy from https://github.com/Robmaister/SharpFont/blob/master/Source/SharpFont/TrueType/Internal/SfntNameRec.cs
    internal struct SfntNameRec
    {
      internal ushort platform_id;
      internal ushort encoding_id;
      internal ushort language_id;
      internal ushort name_id;

      internal IntPtr @string;
      internal uint string_len;
    }

    // copy from https://github.com/Robmaister/SharpFont/blob/master/Source/SharpFont/TrueType/SfntName.cs
    class SfntName
    {
      private SfntNameRec rec;
      internal SfntName(SfntNameRec rec)
      {
            this.rec = rec;
      }

      public ushort PlatformId
      {
            get
            {
                return rec.platform_id;
            }
      }

      public ushort EncodingId
      {
            get
            {
                return rec.encoding_id;
            }
      }

      public ushort LanguageId
      {
            get
            {
                return rec.language_id;
            }
      }

      public ushort NameId
      {
            get
            {
                return rec.name_id;
            }
      }

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

      public string StringAnsi
      {
            get
            {
                return Marshal.PtrToStringAnsi(rec.@string, (int)rec.string_len);
            }
      }

      public IntPtr StringPtr
      {
            get
            {
                return rec.@string;
            }
      }
    }
}





tmdtmdtmdqq 发表于 2023-7-29 19:12:07

调教了一个下午GPT,终于调教出来了。.net原生方法获取Postscript name。支持TTF/OTF/TTC格式。

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

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

namespace TTFParser
{
    class Program
    {
      static void Main(string[] args)
      {
            Console.Write("Input font file path:");
            string filePath = Console.ReadLine();

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

            if (fileData.Length >= 4 && fileData == 0x74 && fileData == 0x74 && fileData == 0x63 && fileData == 0x66)
            {
                // TTC file signature: 0x74 0x74 0x63 0x66 (ttcf)

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

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

                  FontAttributeAnalysis(fileData, fontOffset);
                }
            }
            else
            {
                // OTF file signature: 0x4F 0x54 0x54 0x4F (OTTO)
                // TTF file signature: 0x00 0x01 0x00 0x00
                FontAttributeAnalysis(fileData, 0);
            }
      }

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

            string familyName = null;
            string postscriptName = null;

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

                if (tag == "name")
                {
                  int nameTableOffset = ReadUInt32(fileData, tagOffset + 8);
                  int nameCount = ReadUInt16(fileData, nameTableOffset + 2);
                  int stringOffset = ReadUInt16(fileData, nameTableOffset + 4);

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

                        int platformID = ReadUInt16(fileData, nameRecordOffset);
                        int encodingID = ReadUInt16(fileData, nameRecordOffset + 2);
                        int languageID = ReadUInt16(fileData, nameRecordOffset + 4);
                        int nameID = ReadUInt16(fileData, nameRecordOffset + 6);
                        int nameLength = ReadUInt16(fileData, nameRecordOffset + 8);
                        int nameOffset = nameTableOffset + stringOffset + ReadUInt16(fileData, nameRecordOffset + 10);

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

                        if (nameID == 1) // Family name ID
                        {
                            if (familyName == null)
                            {
                              familyName = ReadString(fileData, nameOffset, nameLength);
                            }
                        }
                        else if (nameID == 6) // Postscript name ID
                        {
                            if (postscriptName == null)
                            {
                              postscriptName = ReadString(fileData, nameOffset, nameLength);
                            }
                        }

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

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

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

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

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

                        
                        for (int k = dictOffset; k < nextDictOffset; k++)
                        {
                            // The PostScript name is stored in the font dictionary with the operator 12 30.
                            if (fileData == 12 && fileData == 30)
                            {
                              // The PostScript name follows the operator and is encoded as a string.
                              int postscriptNameLength = fileData;
                              postscriptName = ReadString(fileData, cffTableOffset + k + 3, postscriptNameLength);
                              break;
                            }

                            // The Family name is stored in the font dictionary with the operator 12 38.
                            if (fileData == 12 && fileData == 38)
                            {
                              // The Family name follows the operator and is encoded as a string.
                              int familyNameLength = fileData;
                              familyName = ReadString(fileData, cffTableOffset + k + 3, familyNameLength);
                              break;
                            }
                        }
                  }
                }
            }

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

      static int ReadUInt16(byte[] data, int offset)
      {
            return (data << 8) | data;
      }

      static int ReadUInt32(byte[] data, int offset)
      {
            return (data << 24) | (data << 16) | (data << 8) | data;
      }

      static string ReadString(byte[] data, int offset, int length)
      {
            return System.Text.Encoding.ASCII.GetString(data, offset, length);
      }
    }
}





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 一樣

(認真的,不是嘲諷)


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 存在

Qianshijiang 发表于 2023-8-24 03:56:35

我的神!{:4_678:}

wzdc 发表于 2023-9-9 16:37:04

本帖最后由 wzdc 于 2023-9-9 16:40 编辑

az
https://www.cdnjson.com/images/2023/09/09/-2023-09-09-163642.png

sdyt6383985 发表于 2023-9-11 23:08:20

哇!发现好东西!感谢楼主分享!

onezhai 发表于 2023-9-19 19:51:34

好感动,这么久了还在更新软件。感恩作者!
页: 43 44 45 46 47 48 49 50 51 52 [53] 54 55 56
查看完整版本: ListAssFonts: 小工具,分析字幕使用的字型 (暫停更新及下載)