WPF 加载诡异的字体无法布局

  • A+
所属分类:.NET技术
摘要

如果在系统里面存在诡异的字体,同时自己的 WPF 中有一个控件尝试使用这个字体放在界面中,那么将会在界面布局过程炸了,整个控件或者整个界面布局都无法继续

如果在系统里面存在诡异的字体,同时自己的 WPF 中有一个控件尝试使用这个字体放在界面中,那么将会在界面布局过程炸了,整个控件或者整个界面布局都无法继续

本文本来是由吕水大大发布的,但是他没空写,于是我就成为了写博客的工具人

有一个用户报告了软件在他的电脑上打不开列出本机字体列表,于是吕水大大就去远程他的设备,在用户的设备上找到了一个诡异的字体,加载这个字体的时候,将会在 MS.Internal.Text.TextInterface.Font.CreateFontFace 抛出异常。而且有趣的是 Win10 的 UWP 版的设置里面是找不到这个字体的,原因是 UWP 版本也会在读取此字体的时候炸了

复现的步骤如下,先从 https://github.com/walterlv/Walterlv.Demo.WpfBadFontCrash 下载代码,在代码仓库里面可以找到 不给糖就捣蛋的万圣节.TTF 这个字体,值得一说的是,诡异的字体有很多,这个字体只是一个例子。双击安装一下这个字体到你的系统上

然后新建一个 WPF 或 UWP 程序,在界面里面添加一个 TextBlock 然后在 TextBlock 里面采用此字体,如下面代码

    <TextBlock Text="ABCDEFGH" FontFamily="不给糖就捣蛋的万圣节" /> 

参与运行此 WPF 程序,将会看到如下提示

System.IO.FileFormatException: Invalid file format.    at MS.Internal.Text.TextInterface.Native.Util.ConvertHresultToException(Int32 hr)    at MS.Internal.Text.TextInterface.Font.CreateFontFace()    at MS.Internal.Text.TextInterface.Font.AddFontFaceToCache()    at MS.Internal.Text.TextInterface.Font.GetFontFace()    at System.Windows.Media.GlyphTypeface..ctor(Font font)    at MS.Internal.FontFace.PhysicalFontFamily.GetGlyphTypeface(FontStyle style, FontWeight weight, FontStretch stretch)    at MS.Internal.FontFace.PhysicalFontFamily.MS.Internal.FontFace.IFontFamily.GetTypefaceMetrics(FontStyle style, FontWeight weight, FontStretch stretch)    at System.Windows.Media.Typeface.ConstructCachedTypeface()    at System.Windows.Media.Typeface.get_CachedTypeface()    at System.Windows.Media.Typeface.CheckFastPathNominalGlyphs(CharacterBufferRange charBufferRange, Double emSize, Single pixelsPerDip, Double scalingFactor, Double widthMax, Boolean keepAWord, Boolean numberSubstitution, CultureInfo cultureInfo, TextFormattingMode textFormattingMode, Boolean isSideways, Boolean breakOnTabs, Int32& stringLengthFit)    at MS.Internal.TextFormatting.SimpleRun.CreateSimpleTextRun(CharacterBufferRange charBufferRange, TextRun textRun, TextFormatterImp formatter, Int32 widthLeft, Boolean emergencyWrap, Boolean breakOnTabs, Double pixelsPerDip)    at MS.Internal.TextFormatting.SimpleRun.Create(FormatSettings settings, CharacterBufferRange charString, TextRun textRun, Int32 cp, Int32 cpFirst, Int32 runLength, Int32 widthLeft, Int32 idealRunOffsetUnRounded, Double pixelsPerDip)    at MS.Internal.TextFormatting.SimpleTextLine.Create(FormatSettings settings, Int32 cpFirst, Int32 paragraphWidth, Double pixelsPerDip)    at MS.Internal.TextFormatting.TextFormatterImp.FormatLineInternal(TextSource textSource, Int32 firstCharIndex, Int32 lineLength, Double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache)    at MS.Internal.TextFormatting.TextFormatterImp.FormatLine(TextSource textSource, Int32 firstCharIndex, Double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache)    at System.Windows.Controls.TextBlock.MeasureOverride(Size constraint)    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)    at System.Windows.UIElement.Measure(Size availableSize)    at System.Windows.Controls.Grid.MeasureOverride(Size constraint)    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)    at System.Windows.UIElement.Measure(Size availableSize)    at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)    at System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)    at System.Windows.UIElement.Measure(Size availableSize)    at System.Windows.Controls.Decorator.MeasureOverride(Size constraint)    at System.Windows.Documents.AdornerDecorator.MeasureOverride(Size constraint)    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)    at System.Windows.UIElement.Measure(Size availableSize)    at System.Windows.Controls.Border.MeasureOverride(Size constraint)    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)    at System.Windows.UIElement.Measure(Size availableSize)    at System.Windows.Window.MeasureOverrideHelper(Size constraint)    at System.Windows.Window.MeasureOverride(Size availableSize)    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)    at System.Windows.UIElement.Measure(Size availableSize)    at System.Windows.Interop.HwndSource.SetLayoutSize()    at System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)    at System.Windows.Interop.HwndSource.set_RootVisual(Visual value)    at System.Windows.Window.SetRootVisual()    at System.Windows.Window.SetRootVisualAndUpdateSTC()    at System.Windows.Window.SetupInitialState(Double requestedTop, Double requestedLeft, Double requestedWidth, Double requestedHeight)    at System.Windows.Window.CreateSourceWindow(Boolean duringShow)    at System.Windows.Window.CreateSourceWindowDuringShow()    at System.Windows.Window.SafeCreateWindowDuringShow()    at System.Windows.Window.ShowHelper(Object booleanBox)    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)    at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) 

如果上面的逻辑是放在 MainWindow 里面,那么意味着刚尝试创建窗口就凉凉了,可以看到窗口都创建失败。如果自己没有写 Dispatcher.UnhandledException 那么应用程序将会退出

代码请看 https://github.com/walterlv/Walterlv.Demo.WpfBadFontCrash

这个坑我报告给了 WPF 官方,请看 WPF can not handle special damaged font · Issue #4283 · dotnet/wpf

对于 UWP 来说也一样,尝试在界面中放和上面 WPF 相同的代码,可以看到设计器给出了提示

WPF 加载诡异的字体无法布局

运行 UWP 应用,将可以看到进入了下面代码

#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION             UnhandledException += (sender, e) =>             {                 if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();             }; #endif 

这里面的 e 的内容没有啥有用的信息,可以看到的代码如下

-		Exception	{"指示输入文件 (例如字体文件) 中的错误。rnrn指示输入文件 (例如字体文件) 中的错误。rn"}	System.Exception {System.Runtime.InteropServices.COMException} 

也就说 UWP 在调用到更底层的时候炸掉了,其实也看不到堆栈。同时在 UWP 如果是在第一个界面中添加以上代码,那么即使在 App.xaml.cs 使用下面代码尝试接住,应用也是继续退出

        public App()         {             InitializeComponent();             Suspending += OnSuspending;             UnhandledException += App_UnhandledException;         }          private void App_UnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)         {             e.Handled = true;         } 

代码放在 githubgitee 欢迎小伙伴访问