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
好感动,这么久了还在更新软件。感恩作者!