使用 new FontFamily 時因字型不存在或非 TrueType 而造成 Exception 的問題

當我們要使用某種字型, 必須採用以下的方式進行: new Font(new FontFamily(fontName), 14f, FontStyle.Regular); 上面 fontName 是指輸入的字型, 例如 Arial。但是當你把程式部署到客戶端之後, 會有一個你可能想像不到的問題, 那就是, 如果對方機器上並未安裝此字型, 這時會產生一個 runtime 的 Argument Exception...

當我們要使用某種字型, 必須採用以下的方式進行:

new Font(new FontFamily(fontName), 14f, FontStyle.Regular);

上面 fontName 是指輸入的字型, 例如 Arial。

但是當你把程式部署到客戶端之後, 會有一個你可能想像不到的問題, 那就是, 如果對方機器上並未安裝此字型, 這時會產生一個 runtime 的 Argument Exception。

可惜的是, .Net Framework 並未提供類似 FontFamily.Exists() 的方法來做事先檢查。取而代之的, 你必須採用 InstalledFontCollection 來進行列舉, 然後進行比對。程式如下:

FontFamily ff;
InstalledFontCollection installed = new InstalledFontCollection(); // Enumerate installed fonts
if (Array.Exists(installed.Families, f => f.Name.ToLower().Trim() == fontName.ToLower().Trim()))
    ff = new FontFamily(fontName); // In case the assigned font does not exist
else                               // If you don't do this check, exception may occur
    ff = FontFamily.GenericSansSerif;  // Use an alternative font

font = new Font(ff, 14f, FontStyle.Regular);

此外, 如果你寫的是 Windows Form, 你最常遇到的問題就是使用者選取的字型通常不是 TrueType 字型, 而此時又會造成另一種 Exception。很可惜的, 在 .Net 中並沒有提供任何可以判斷字型是否為 TrueType 的功能(這真的不合理, 哪有會發出 Exception, 卻不提供預防功能的?)。我試過使用 PInvoke 去調用 GDI32 的函式, 但怎樣都試不成功:

    class Fonts
    {
        const int TMPF_TRUETYPE = 0x4;

        public static bool isFontTrueType(IntPtr handle, string fontname)
        {
            LOGFONT lf = new LOGFONT();
            lf.lfFaceName = "Microsoft Sans Serif";
            TEXTMETRIC tm;
            long newFont;
            newFont = CreateFontIndirect(ref lf);

            long dummy = GetTextMetrics(handle, out tm);
            bool istruetype = (tm.tmPitchAndFamily & TMPF_TRUETYPE) > 0;
            return istruetype;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct LOGFONT
        {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string lfFaceName;
        }

        [Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct TEXTMETRIC
        {
            public int tmHeight;
            public int tmAscent;
            public int tmDescent;
            public int tmInternalLeading;
            public int tmExternalLeading;
            public int tmAveCharWidth;
            public int tmMaxCharWidth;
            public int tmWeight;
            public int tmOverhang;
            public int tmDigitizedAspectX;
            public int tmDigitizedAspectY;
            public char tmFirstChar;
            public char tmLastChar;
            public char tmDefaultChar;
            public char tmBreakChar;
            public byte tmItalic;
            public byte tmUnderlined;
            public byte tmStruckOut;
            public byte tmPitchAndFamily;
            public byte tmCharSet;
        }

        [DllImport("Gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "GetTextMetricsW")]
        static extern long GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm); 

        [DllImport("gdi32.dll")]
        static extern long CreateFontIndirect(
            [In] ref LOGFONT lplf);

        [DllImport("gdi32", EntryPoint = "CreateFontW")]
        private static extern IntPtr CreateFontW(
            [In] Int32 nHeight,
            [In] Int32 nWidth,
            [In] Int32 nEscapement,
            [In] Int32 nOrientation,
            [In] Int32 fnWeight,
            [In] UInt32 fdwItalic,
            [In] UInt32 fdwUnderline,
            [In] UInt32 fdwStrikeOut,
            [In] UInt32 fdwCharSet,
            [In] UInt32 fdwOutputPrecision,
            [In] UInt32 fdwClipPrecision,
            [In] UInt32 fdwQuality,
            [In] UInt32 fdwPitchAndFamily,
            [In] IntPtr lpszFace);
    }

我不知道是不是因為有什麼寫法錯誤的原故, isFontTrueType 傳回來永遠都是 false。如果你看得出問題在哪裡的話, 麻煩你告訴我一下。

在以上程式能夠正常使用之前, 最簡單的做法就是利用 try.. catch 去捕捉這佪 Exception 了。


Dev 2Share @ 點部落