我一直在寻找一种使用.Net框架列出给定字体的有效字体样式的方法(即使我必须调用gdi32或其他API),因为并非所有字体都属于System.Drawing.FontStyle枚举值(粗体,斜体,常规,删除线,下划线)。 Segoe UI是一种不符合要求的字体的完美示例,它是TrueType Microsoft字体,其字体样式为:Regular,Semibold,Light,Bold,Italic和BoldItalic。另一个示例是Arial,其具有:Regular,Narrow,Italic,Bold,Bold Italic,Narrow Bold,Narrow Bold Italic和Narrow Italic。
在Windows 7中(可能也有Vista,但我没有机器要检查),当打开资源管理器并浏览到%SystemRoot%\ Fonts时,您会看到一列“ Font style”,其中列出了所有每种字体的可用样式,这告诉我,至少在API调用中,肯定有一种方法可以做到这一点。
最终,我希望枚举FontFamily列表,然后列出每个家族的每种字体样式。以下是列出所有字体系列的示例代码,如果有人可以提供帮助列出每个字体可用的字体样式,我将不胜感激。如果我要以错误的方式进行操作,我肯定会提出建议。
Drawing.Text.InstalledFontCollection ifc = new Drawing.Text.InstalledFontCollection();
foreach ( FontFamily ff in ifc.Families )
{
Console.WriteLine(ff.ToString());
// Something like this would be nice, but AFAIK nothing similar exists
/*
foreach ( FontStyle style in ff.Styles )
Console.WriteLine(style.ToString());
*/
}
好的,下面是很多代码。主要是因为TTF结构和TTF文件的Endianess。该代码最初不是我的代码,它来自一些我移植到VB.NET并更改了一些内容的来源。有关获取字体名称的C ++版本,请参见this page。
此代码通过注册表读取安装的字体(无论是%windir%\ fonts还是其他字体,过滤器仅获取扩展名为.ttf的字体(例如,忽略.fon和.ttc),然后传递这些字体文件例程GetFontDetails
的路径,该例程读取并获取字体名称(uNameID#1)和字体子系列(又称为Style,uNameID#2)。如果您有兴趣获得比这些更多的属性,请转到Microsoft字体网站上的name - Naming Table,然后在浏览器中搜索名称ID。然后,它将“字体名称”,“字体子系列”和“字体路径”踢出到控制台窗口。
创建一个新的VB.NET控制台应用程序,并将以下内容粘贴到Module1代码中,然后按F5。
不用多说:
Imports System.Linq
Imports System.IO
Imports System.Text
Module Module1
Sub Main()
Dim allInstalledFonts = From e In My.Computer.Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts").GetValueNames
Select My.Computer.Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts").GetValue(e)
Dim ttfFonts = From e In allInstalledFonts.Where(Function(e) e.ToString.EndsWith(".ttf") Or e.ToString.EndsWith(".otf"))
Dim ttfFontsPaths = From e In ttfFonts.Select(Function(e) If(Path.GetPathRoot(e.ToString) = "", Environment.GetFolderPath(Environment.SpecialFolder.Fonts) & "\" & e.ToString, e.ToString))
Dim fonts = From e As String In ttfFontsPaths Select GetFontDetails(e.ToString)
For Each f As InstalledFont In fonts
Console.WriteLine("Name: " & f.FontName & ", SubFamily: " & f.FontSubFamily & ", Path: " & f.FontPath)
Next
Console.ReadLine()
End Sub
Public Class InstalledFont
Property FontName As String
Property FontSubFamily As String
Property FontPath As String
Sub New(ByVal name As String, ByVal subfamily As String, ByVal path As String)
FontName = name
FontSubFamily = subfamily
FontPath = path
End Sub
End Class
Public Function GetFontDetails(ByVal fontFilePath As String) As InstalledFont
Dim FontName As String = String.Empty
Dim FontSubFamily As String = String.Empty
Dim encStr = "UTF-8"
Dim strRet As String = String.Empty
Using fs As New FileStream(fontFilePath, FileMode.Open, FileAccess.Read)
Dim ttOffsetTable As New TT_OFFSET_TABLE
With ttOffsetTable
.uMajorVersion = ReadUShort(fs)
.uMinorVersion = ReadUShort(fs)
.uNumOfTables = ReadUShort(fs)
.uSearchRange = ReadUShort(fs)
.uEntrySelector = ReadUShort(fs)
.uRangeShift = ReadUShort(fs)
End With
If ttOffsetTable.uMajorVersion <> 1 Or ttOffsetTable.uMinorVersion <> 0 Then
Return Nothing
End If
Dim tblDir As New TT_TABLE_DIRECTORY
Dim found As Boolean = False
For i As Integer = 0 To ttOffsetTable.uNumOfTables
With tblDir
.Initialize()
fs.Read(.szTag, 0, .szTag.Length)
.uCheckSum = ReadULong(fs)
.uOffset = ReadULong(fs)
.uLength = ReadULong(fs)
End With
Dim enc As Encoding = Encoding.GetEncoding(encStr)
Dim s As String = enc.GetString(tblDir.szTag)
If StrComp(s, "name") = 0 Then
found = True
Exit For
End If
Next
If Not found Then Return Nothing
fs.Seek(tblDir.uOffset, SeekOrigin.Begin)
Dim ttNTHeader As New TT_NAME_TABLE_HEADER
With ttNTHeader
.uFSelector = ReadUShort(fs)
.uNRCount = ReadUShort(fs)
.uStorageOffset = ReadUShort(fs)
End With
Dim ttRecord As New TT_NAME_RECORD
For j As Integer = 0 To ttNTHeader.uNRCount
With ttRecord
.uPlatformID = ReadUShort(fs)
.uEncodingID = ReadUShort(fs)
.uLanguageID = ReadUShort(fs)
.uNameID = ReadUShort(fs)
.uStringLength = ReadUShort(fs)
.uStringOffset = ReadUShort(fs)
End With
If ttRecord.uNameID > 2 Then Exit For
Dim nPos As Integer = fs.Position
fs.Seek(tblDir.uOffset + ttRecord.uStringOffset + ttNTHeader.uStorageOffset, SeekOrigin.Begin)
Dim buf(ttRecord.uStringLength - 1) As Byte
fs.Read(buf, 0, ttRecord.uStringLength)
Dim enc As Encoding
If ttRecord.uEncodingID = 3 Or ttRecord.uEncodingID = 1 Then
enc = Encoding.BigEndianUnicode
Else
enc = Encoding.UTF8
End If
strRet = enc.GetString(buf)
If ttRecord.uNameID = 1 Then FontName = strRet
If ttRecord.uNameID = 2 Then FontSubFamily = strRet
fs.Seek(nPos, SeekOrigin.Begin)
Next
Return New InstalledFont(FontName, FontSubFamily, fontFilePath)
End Using
End Function
Public Structure TT_OFFSET_TABLE
Public uMajorVersion As UShort
Public uMinorVersion As UShort
Public uNumOfTables As UShort
Public uSearchRange As UShort
Public uEntrySelector As UShort
Public uRangeShift As UShort
End Structure
Public Structure TT_TABLE_DIRECTORY
Public szTag() As Byte
Public uCheckSum As UInt32
Public uOffset As UInt32
Public uLength As UInt32
Public Sub Initialize()
ReDim szTag(3)
End Sub
End Structure
Public Structure TT_NAME_TABLE_HEADER
Public uFSelector As UShort
Public uNRCount As UShort
Public uStorageOffset As UShort
End Structure
Public Structure TT_NAME_RECORD
Public uPlatformID As UShort
Public uEncodingID As UShort
Public uLanguageID As UShort
Public uNameID As UShort
Public uStringLength As UShort
Public uStringOffset As UShort
End Structure
Private Function ReadChar(ByRef fs As FileStream, ByVal characters As Integer) As UInt16
Dim s(characters) As String
Dim buf(CByte(s.Length)) As Byte
buf = ReadAndSwap(fs, buf.Length)
Return BitConverter.ToUInt16(buf, 0)
End Function
Private Function ReadByte(ByRef fs As FileStream) As UInt16
Dim buf(10) As Byte
buf = ReadAndSwap(fs, buf.Length)
Return BitConverter.ToUInt16(buf, 0)
End Function
Private Function ReadUShort(ByRef fs As FileStream) As UInt16
Dim buf(1) As Byte
buf = ReadAndSwap(fs, buf.Length)
Return BitConverter.ToUInt16(buf, 0)
End Function
Private Function ReadULong(ByRef fs As FileStream) As UInt32
Dim buf(3) As Byte
buf = ReadAndSwap(fs, buf.Length)
Return BitConverter.ToUInt32(buf, 0)
End Function
Private Function ReadAndSwap(ByRef fs As FileStream, ByVal size As Integer) As Byte()
Dim buf(size - 1) As Byte
fs.Read(buf, 0, buf.Length)
Array.Reverse(buf)
Return buf
End Function
End Module
感谢宅男和所有...拿了Otaku的代码并将其移植到c#中以供感兴趣的任何人使用,但是没有进行OTF更新。主要更改只是字节数组调整(VB pads数组),lambda表达式和常规语法更改...
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Win32;
namespace InstalledFontsInSystem
{
class InstalledFont
{
#region InstalledFont Parameters
string _fontName = string.Empty;
string _fontSubFamily = string.Empty;
string _fontPath = string.Empty;
#endregion
#region InstalledFont Constructor
public InstalledFont(string fontName, string fontSubFamily, string fontPath)
{
_fontName = fontName;
_fontSubFamily = fontSubFamily;
_fontPath = fontPath;
}
#endregion
#region InstalledFont Properties
public string FontName { get { return _fontName; } set { _fontName = value; } }
public string FontSubFamily { get { return _fontSubFamily; } set { _fontSubFamily = value; } }
public string FontPath { get { return _fontPath; } set { _fontPath = value; } }
#endregion
}
class Program
{
static void Main(string[] args)
{
var allInstalledFonts = from e in Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", false).GetValueNames()
select Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts").GetValue(e);
var ttfFonts = from e in allInstalledFonts.Where(e => (e.ToString().EndsWith(".ttf") || e.ToString().EndsWith(".otf"))) select e;
var ttfFontsPaths = from e in ttfFonts.Select(e => (Path.GetPathRoot(e.ToString()) == "") ? Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "\\" + e.ToString() : e.ToString()) select e;
var fonts = from e in ttfFontsPaths.Select(e => GetFontDetails(e.ToString())) select e;
foreach (InstalledFont f in fonts)
{
if(f != null)
Console.WriteLine("Name: " + f.FontName + ", SubFamily: " + f.FontSubFamily + ", Path: " + f.FontPath);
}
Console.ReadLine();
}
static public InstalledFont GetFontDetails(string fontFilePath)
{
string FontName = string.Empty;
string FontSubFamily = string.Empty;
string encStr = "UTF-8";
string strRet = string.Empty;
using (FileStream fs = new FileStream(fontFilePath, FileMode.Open, FileAccess.Read))
{
TT_OFFSET_TABLE ttOffsetTable = new TT_OFFSET_TABLE()
{
uMajorVersion = ReadUShort(fs),
uMinorVersion = ReadUShort(fs),
uNumOfTables = ReadUShort(fs),
uSearchRange = ReadUShort(fs),
uEntrySelector = ReadUShort(fs),
uRangeShift = ReadUShort(fs),
};
if (ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0)
{
return null;
}
TT_TABLE_DIRECTORY tblDir = new TT_TABLE_DIRECTORY();
bool found = false;
for (int i = 0; i <= ttOffsetTable.uNumOfTables; i++)
{
tblDir = new TT_TABLE_DIRECTORY();
tblDir.Initialize();
fs.Read(tblDir.szTag, 0, tblDir.szTag.Length);
tblDir.uCheckSum = ReadULong(fs);
tblDir.uOffset = ReadULong(fs);
tblDir.uLength = ReadULong(fs);
Encoding enc = Encoding.GetEncoding(encStr);
string s = enc.GetString(tblDir.szTag);
if (s.CompareTo("name") == 0)
{
found = true;
break;
}
}
if (!found) return null;
fs.Seek(tblDir.uOffset, SeekOrigin.Begin);
TT_NAME_TABLE_HEADER ttNTHeader = new TT_NAME_TABLE_HEADER
{
uFSelector = ReadUShort(fs),
uNRCount = ReadUShort(fs),
uStorageOffset = ReadUShort(fs)
};
TT_NAME_RECORD ttRecord = new TT_NAME_RECORD();
for (int j = 0; j <= ttNTHeader.uNRCount; j++)
{
ttRecord = new TT_NAME_RECORD()
{
uPlatformID = ReadUShort(fs),
uEncodingID = ReadUShort(fs),
uLanguageID = ReadUShort(fs),
uNameID = ReadUShort(fs),
uStringLength = ReadUShort(fs),
uStringOffset = ReadUShort(fs)
};
if (ttRecord.uNameID > 2) { break; }
long nPos = fs.Position;
fs.Seek(tblDir.uOffset + ttRecord.uStringOffset + ttNTHeader.uStorageOffset, SeekOrigin.Begin);
byte[] buf = new byte[ttRecord.uStringLength];
fs.Read(buf, 0, ttRecord.uStringLength);
Encoding enc;
if (ttRecord.uEncodingID == 3 || ttRecord.uEncodingID == 1)
{
enc = Encoding.BigEndianUnicode;
}
else
{
enc = Encoding.UTF8;
}
strRet = enc.GetString(buf);
if (ttRecord.uNameID == 1) { FontName = strRet; }
if (ttRecord.uNameID == 2) { FontSubFamily = strRet; }
fs.Seek(nPos, SeekOrigin.Begin);
}
return new InstalledFont(FontName, FontSubFamily, fontFilePath);
}
}
public struct TT_OFFSET_TABLE
{
public ushort uMajorVersion;
public ushort uMinorVersion;
public ushort uNumOfTables;
public ushort uSearchRange;
public ushort uEntrySelector;
public ushort uRangeShift;
}
public struct TT_TABLE_DIRECTORY
{
public byte[] szTag;
public UInt32 uCheckSum;
public UInt32 uOffset;
public UInt32 uLength;
public void Initialize()
{
szTag = new byte[4];
}
}
public struct TT_NAME_TABLE_HEADER
{
public ushort uFSelector;
public ushort uNRCount;
public ushort uStorageOffset;
}
public struct TT_NAME_RECORD
{
public ushort uPlatformID;
public ushort uEncodingID;
public ushort uLanguageID;
public ushort uNameID;
public ushort uStringLength;
public ushort uStringOffset;
}
static private UInt16 ReadChar(FileStream fs, int characters)
{
string[] s = new string[characters];
byte[] buf = new byte[Convert.ToByte(s.Length)];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt16(buf, 0);
}
static private UInt16 ReadByte(FileStream fs)
{
byte[] buf = new byte[11];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt16(buf, 0);
}
static private UInt16 ReadUShort(FileStream fs)
{
byte[] buf = new byte[2];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt16(buf, 0);
}
static private UInt32 ReadULong(FileStream fs)
{
byte[] buf = new byte[4];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt32(buf, 0);
}
static private byte[] ReadAndSwap(FileStream fs, int size)
{
byte[] buf = new byte[size];
fs.Read(buf, 0, buf.Length);
Array.Reverse(buf);
return buf;
}
}
}
HTH戴夫
我发现自己和这里的每个人都在同一条船上。我们所有人都希望这种神奇的本机字体对话框分组行为,并且没有API支持它!不幸的是,当前答案的作用与GDI +和System.Drawing相同:将Segoe UI Semibold
或Arial Black
隔离为它们自己的单字体家族,并失去与Segoe UI
和Arial
的连接。我想这使它成为Arial家族的败类...
我开始了一个开源项目,以解决此问题以及OTF / TTC解析,并提供一个不错的API。它处理了我扔给它的所有极端情况。随时复制或修改代码。 https://github.com/jnm2/TypographicFonts
您的代码示例成为:
foreach (var ff in TypographicFontFamily.InstalledFamilies)
{
Console.WriteLine(ff.Name);
foreach (var font in ff.Fonts)
{
Console.WriteLine(font.SubFamily);
// var gdiPlusFont = new Font(f.Name, 16);
}
}
如果您真正感兴趣的是将样式应用于现有的字体对象:
var semiboldFont = new Font("Segoe UI", 9).With(TypographicFontWeight.Semibold);
Console.WriteLine(semiboldFont.Name); // "Segoe UI Semibold"
您也不限于已安装的字体。要解析字体文件中的字体,请使用TypographicFont.FromFile
。
感谢Todd Main的开拓。
如何使用FontFamily.IsStyleAvailable检查每个字体系列的字体样式是否可用。您可以签入感兴趣的字体样式(如果可用),然后列出支持的样式。
戴夫,注释掉此版本检查:
if (ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0)
{
return null;
}
对我来说,这是窍门,我现在也可以获取.otf字体名称...
[还添加了两个ToLower
IEnumerable<object> ttfFonts = from e in allInstalledFonts.Where(e => (e.ToString().ToLower().EndsWith(".ttf") || e.ToString().ToLower().EndsWith(".otf"))) select e;
我安装的某些字体中所有导致==子句的大写字母都没有找到,因为ttf == TTF比较。
希望对任何人都有帮助!