AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
24.75k stars 2.14k forks source link

Better logging or exception message to troubleshoot font loading #13040

Open MrJul opened 10 months ago

MrJul commented 10 months ago

When loading a font file by family name, it would be awesome to have some optional verbose logging to diagnose font issues, or a better exception than Could not create glyphtypeface.

As seen on Telegram, one user was struggling to load Font Awesome 6 Free Regular. Most tools you can find will report a font family name of Font Awesome 6 Free Regular for the Font Awesome 6 Free-Regular-400.otf file, but Skia reports Font Awesome 6 Free as the name instead.

It turns out that there are two major font names in OTF files:

The Open Font Format standard ISO/IEC 14496-22:2019 specifies that the typographic family name is ID 16 if present, ID 1 otherwise, which Skia respects. It's quite hard for an user to get the real name quickly, since every tool in existence seems to display the name with ID 1.

When searching through a FontCollection for a given family name, Avalonia should ideally report the other family names it found in the collection, via logging or through the exception.

ArtjomP commented 3 months ago

I struggle with the same error. Unfortunately, it happens at random moments and I checked that all the fonts imported well.

Could not create glyphTypeface.
System.InvalidOperationException: Could not create glyphTypeface.
   at Avalonia.Media.Typeface.get_GlyphTypeface()
   at Avalonia.Media.TextFormatting.TextRunProperties.get_CachedGlyphTypeface()
   at Avalonia.Media.TextFormatting.TextCharacters.CreateShapeableRun(ReadOnlyMemory`1 text, TextRunProperties defaultProperties, SByte biDiLevel, FontManager fontManager, TextRunProperties& previousProperties)
   at Avalonia.Media.TextFormatting.TextCharacters.GetShapeableCharacters(ReadOnlyMemory`1 text, SByte biDiLevel, FontManager fontManager, TextRunProperties& previousProperties, RentedList`1 results)
   at Avalonia.Media.TextFormatting.TextFormatterImpl.CoalesceLevels(IReadOnlyList`1 textCharacters, ReadOnlySpan`1 levels, FontManager fontManager, RentedList`1 processedRuns)
   at Avalonia.Media.TextFormatting.TextFormatterImpl.ShapeTextRuns(IReadOnlyList`1 textRuns, TextParagraphProperties paragraphProperties, FormattingObjectPool objectPool, FontManager fontManager, FlowDirection& resolvedFlowDirection)
   at Avalonia.Media.TextFormatting.TextFormatterImpl.FormatLine(ITextSource textSource, Int32 firstTextSourceIndex, Double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak)
   at Avalonia.Media.TextFormatting.TextLayout.CreateTextLines()
   at Avalonia.Media.TextFormatting.TextLayout..ctor(String text, Typeface typeface, Double fontSize, IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextTrimming textTrimming, TextDecorationCollection textDecorations, FlowDirection flowDirection, Double maxWidth, Double maxHeight, Double lineHeight, Double letterSpacing, Int32 maxLines, IReadOnlyList`1 textStyleOverrides)
   at Avalonia.Controls.Presenters.TextPresenter.CreateTextLayoutInternal(Size constraint, String text, Typeface typeface, IReadOnlyList`1 textStyleOverrides)
   at Avalonia.Controls.Presenters.TextPresenter.CreateTextLayout()
   at Avalonia.Controls.Presenters.TextPresenter.get_TextLayout()
   at Avalonia.Controls.Presenters.TextPresenter.MeasureOverride(Size availableSize)
   at Avalonia.Layout.Layoutable.MeasureCore(Size availableSize)
   at Avalonia.Layout.Layoutable.Measure(Size availableSize)
   at Avalonia.Layout.LayoutHelper.MeasureChild(Layoutable control, Size availableSize, Thickness padding, Thickness borderThickness)
   at Avalonia.Controls.Border.MeasureOverride(Size availableSize)
   at Avalonia.Layout.Layoutable.MeasureCore(Size availableSize)
   at Avalonia.Layout.Layoutable.Measure(Size availableSize)
   at Avalonia.Layout.LayoutManager.Measure(Layoutable control)
   at Avalonia.Layout.LayoutManager.ExecuteMeasurePass()
   at Avalonia.Layout.LayoutManager.InnerLayoutPass()
   at Avalonia.Layout.LayoutManager.ExecuteLayoutPass()
   at Avalonia.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at Avalonia.Media.MediaContext.RenderCore()
   at Avalonia.Media.MediaContext.Render()
   at Avalonia.Threading.DispatcherOperation.InvokeCore()
   at Avalonia.Threading.DispatcherOperation.Execute()
   at Avalonia.Threading.Dispatcher.ExecuteJob(DispatcherOperation job)
   at Avalonia.Threading.Dispatcher.ExecuteJobsCore(Boolean fromExplicitBackgroundProcessingCallback)
   at Avalonia.Threading.Dispatcher.Signaled()
   at Avalonia.Native.Interop.Impl.__MicroComIAvnPlatformThreadingInterfaceEventsVTable.Signaled(Void* this)
--- End of stack trace from previous location ---
   at Avalonia.Native.DispatcherImpl.RunLoop(CancellationToken token)
   at Avalonia.Threading.DispatcherFrame.Run(IControlledDispatcherImpl impl)
   at Avalonia.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at Avalonia.Threading.Dispatcher.MainLoop(CancellationToken cancellationToken)
   at Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime.Start(String[] args)
   at Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(AppBuilder builder, String[] args, ShutdownMode shutdownMode)
   at a.b.Main(String[] a)
maxkatz6 commented 3 months ago

Current exception message is much better after @ArtjomP changes. But this issues also mentions Avalonia should ideally report the other family names it found in the collection, via logging or through the exception which wasn't implemented. But also, is harder to implement with current infra, as this part of the logic is hidden somewhere in skia code.

MrJul commented 3 months ago

Avalonia iterates the font collection in FontManager. To get back to the original example, we know that Skia loaded a font named Font Awesome 6 Free even if we don't have the details to how it got this name. Avalonia skipped that font since its family name didn't match the requested one, and we can report that through trace logging.

That being said, the changes in the exception message should really help! Maybe we won't need that extra logging.