2010-09-03 10 views
7

Ich habe nach einer Möglichkeit gesucht, die gültigen Schriftstile für eine bestimmte Schriftart mit dem .Net-Framework aufzulisten (auch wenn ich gdi32 oder eine andere API pinnen muss), da nicht alle Schriftarten in den System.Drawing.FontStyle fallen Aufzählungswerte (Fett, Kursiv, Normal, Strikeout, Unterstrichen). Ein perfektes Beispiel für eine Schriftart, die nicht in die Rechnung passt, ist Segoe UI, eine TrueType-Microsoft-Schriftart mit den Schriftarten Regular, Semibold, Light, Bold, Italic und BoldItalic. Ein weiteres Beispiel ist Arial mit den folgenden Optionen: Normal, Schmal, Kursiv, Fett, Fett Kursiv, Schmal Fett, Schmal Fett Kursiv und Schmal Kursiv..Net enumerate Winforms Schriftarten?

In Windows 7 (wahrscheinlich Vista, aber ich habe keine Maschine zu überprüfen), wenn Sie den Explorer öffnen und zu% SystemRoot% \ Fonts durchsuchen, sehen Sie eine Spalte namens "Font style", die alle auflistet von den verfügbaren Stilen für jede Schriftart, was mir sagt, dass es definitiv eine Möglichkeit gibt, dies zu tun, zumindest durch API-Aufrufe.

Letztendlich möchte ich die FontFamily-Liste aufzählen und dann jeden Schriftstil für jede Familie auflisten. Im Folgenden finden Sie Beispielcode für die Auflistung aller Schriftfamilien. Wenn jemand Hilfe bei der Auflistung der verfügbaren Schriftarten für jede Familie geben könnte, würde ich mich darüber freuen. Wenn ich das falsch mache, bin ich definitiv offen für Vorschläge.

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()); 
    */ 
} 
+0

Eine Sache zu beachten: Ein anderes Beispiel, das zeigt, was ich tun möchte, wäre einfach das Textwerkzeug in Photoshop zu öffnen, das eine separate Dropdown-Liste mit allen Schriftstilen enthält. Die Art, wie mspaint es tut, wo es die speziellen Familien trennt (Segoe UI Light, Segoe UI Semibold), wäre ebenfalls akzeptabel, aber so funktioniert InstalledFontCollection anscheinend nicht, so dass dieser Weg möglicherweise über API-Aufrufe vollständig erfolgen muss. – tcnolan

+0

Ich habe hier falsch verstanden ... die Art, wie mspaint funktioniert, ist eigentlich genau so, wie mein Beispielcode funktioniert. Das ist _not_ tatsächlich, was ich suche. Entschuldigung ... Ich denke nicht 100% klar. O_O – tcnolan

+0

Ich musste schon seit einiger Zeit eine Lösung in Bezug auf genau diese Sache abschließen - Ihre Frage veranlasst mich, dies zu tun. Meine Lösung verwendet nicht "EnumFontFamiliesEx" - vielmehr liest es die Registrierung und durchläuft dann alle Schriftarten, um sie und ihre Informationen zu finden, inkl. Stil (d. H. Unterfamilie). Ich mache das, weil ich direkt auf die physische Schriftart zugreifen muss, nachdem ich die richtige gefunden habe, um sie in mein Programm einzubetten. Oh, und ich mache es in VB.NET statt C#. Sobald ich es habe, poste ich den Code. –

Antwort

5

Okay, das wird eine Menge Code unten sein. In erster Linie ist es wegen der TTF-Strukturen und Endianess der TTF-Dateien. Der Code stammt ursprünglich nicht von mir, er stammt aus ein paar Quellen, die ich nach VB.NET portiert habe und einige Dinge geändert habe. Unter this page finden Sie eine C++ - Version, die den Namen der Schriftart erhält.

Dieser Code liest über die Registrierung für installierten Schriftarten (ob in% windir% \ Fonts oder anderswo), Filter nur bekommen diejenigen mit der Erweiterung von .ttf (zB .fon und .ttc ignoriert werden) und dann geht es diese Schriftdateipfade zu einer Routine GetFontDetails, die den Schriftartnamen (uNameID # 1) und die Schriftunterfamilie (auch bekannt als Stil, uNameID # 2) liest und abruft. Wenn Sie mehr Eigenschaften als diese erhalten möchten, gehen Sie zu name - Naming Table auf Microsofts Typography-Website und suchen Sie in Ihrem Browser nach Name IDs. Dann werden Fontname, Font SubFamily und Font Path zum Konsolenfenster hinzugefügt.

Erstellen Sie eine neue VB.NET Console-App und fügen Sie den folgenden Code in Module1 ein und drücken Sie F5.

Ohne weitere Umschweife:

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 
+0

Genau das habe ich gesucht ...obwohl es ein wenig enttäuschend ist, gibt es keine Möglichkeit, dies über API-Aufrufe zu tun :(Vielen Dank! – tcnolan

+0

+1. Ich habe seit langem gesucht. Enttäuschend kann es ttc oder otf Dateien nicht lesen. – Stan

+0

@ ChickenDinner: Ich habe den Code geändert, so dass er auch OTF-Dateien lesen kann.Sie wissen immer noch nicht, was Sie mit TTC-Dateien machen sollen.Wenn ich es herausfinden werde, aktualisiere ich den Code hier: –

1

Wie wäre es mit FontFamily.IsStyleAvailable zu überprüfen, ob die Schriftart zur Verfügung steht oder nicht für jede Schriftfamilie. Sie können interessierte Schriftstile einchecken (falls verfügbar) und dann unterstützte Stile auflisten.

+0

Das ist nicht das Gleiche wie das, was er fragt. Dies sind Dekorationsstile, keine Schriftstile. –

+0

Korrekt Otaku, das ist ein anderes Thema. IsStyleAvailable war das erste, was ich erforschte, aber ich erkannte schnell, dass ich mit Schriftarten wie "Extra Bold", "Semi Bold", "Ultra Light" oder sogar mit YeomanGothicRR "EngravedAntique" zu tun hatte wäre mit FontStyle enum und IsStyleAvailable nicht möglich. – tcnolan

+0

@tnolan - Ich denke, dass Sie Win API dafür verwenden müssen. Überprüfen Sie die EnumFontFamiliesEx-Funktion und die ENUMLOGFONTEX-Struktur. Das letztere hat eine Stileigenschaft, die Ihnen geben wird, was Sie wollen. – VinayC

2

Dank Otaku und All ... Nahm Otaku Code und portiert C# für jeden einzelnen, der interessiert ist ... aber nicht bekommen, um die OTF-Update. Die wichtigsten Änderungen waren nur Byte-Array Anpassungen (VB-Pads Arrays), Lambda-Ausdrücke und die üblichen Syntax Änderungen ...

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 Dave

0

Dave, diese Versionsprüfung Kommentierung aus:

  if (ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0) 
      { 
       return null; 
      } 

hat den Trick für mich gemacht, ich kann .otf Schriftartnamen jetzt auch bekommen ...

auch noch ein paar ToLower (n)

IEnumerable<object> ttfFonts = from e in allInstalledFonts.Where(e => (e.ToString().ToLower().EndsWith(".ttf") || e.ToString().ToLower().EndsWith(".otf"))) select e; 

Einige meiner installierten Schriftarten, wo alle in den Schutzkappen der == Klausel nichts verursacht wegen der ttf == TTF Vergleich findet.

Hoffe es hilft jedem!

0

Ich befand mich im selben Boot wie alle hier. Wir alle wollen dieses magische native Schriftdialog-Gruppierungsverhalten, und es gibt keine APIs, die es unterstützen! Leider hat die aktuelle Antwort das gleiche getan, was GDI + und System.Drawing tun: Isolieren Sie Segoe UI Semibold oder Arial Black in einzelne Schriftfamilien und verlieren Sie die Verbindung mit Segoe UI und Arial. Ich denke, das macht es zum Schwarzen Schaf der Arial-Familie ...

Ich habe ein Open-Source-Projekt gestartet, um dieses und das OTF/TTC-Parsing aufzulösen und eine nette API bereitzustellen. Es behandelt alle Eckfälle, die ich darauf geworfen habe. Fühlen Sie sich frei, den Code zu kopieren oder zu ändern. https://github.com/jnm2/TypographicFonts

Ihr Codebeispiel wird:

foreach (var ff in TypographicFontFamily.InstalledFamilies) 
{ 
    Console.WriteLine(ff.Name); 

    foreach (var font in ff.Fonts) 
    { 
     Console.WriteLine(f.Subfamily); 
     // var gdiPlusFont = new Font(f.Name, 16); 
    } 
} 

Wenn das, was du bist wirklich interessiert ist, einen Stil zu einem vorhandenen Font-Objekt anwenden:

var semiboldFont = new Font("Segoe UI", 9).With(TypographicFontWeight.Semibold); 
Console.WriteLine(semiboldFont.Name); // "Segoe UI Semibold" 

Sie sind nicht installierten Schriften eingeschränkt , entweder. Verwenden Sie TypographicFont.FromFile, um Zeichensätze aus einer Schriftartdatei zu analysieren.

Dank Todd Main für Pionierarbeit.