public static CanvasGeometry CreateGeometry(
            float size,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTextLayoutAnalysis analysis,
            CanvasTypography typography)
        {
            CanvasDevice device = Utils.CanvasDevice;

            /* SVG Exports render at fixed size - but a) they're vectors, and b) they're
             * inside an auto-scaling viewport. So render-size is *largely* pointless */
            float canvasH = size, canvasW = size, fontSize = size;

            using (CanvasTextLayout layout = new CanvasTextLayout(device, $"{selectedChar.Char}", new CanvasTextFormat
            {
                FontSize = fontSize,
                FontFamily = selectedVariant.Source,
                FontStretch = selectedVariant.FontFace.Stretch,
                FontWeight = selectedVariant.FontFace.Weight,
                FontStyle = selectedVariant.FontFace.Style,
                HorizontalAlignment = CanvasHorizontalAlignment.Center
            }, canvasW, canvasH))
            {
                layout.SetTypography(0, 1, typography);
                layout.Options = analysis.GlyphFormats.Contains(GlyphImageFormat.Svg) ? CanvasDrawTextOptions.EnableColorFont : CanvasDrawTextOptions.Default;

                return(CanvasGeometry.CreateText(layout));
            }
        }
Esempio n. 2
0
        private static string GetFileName(FontVariant font, ExportNamingScheme scheme)
        {
            string fileName = null;
            string ext      = ".ttf";

            var src = DirectWrite.GetFileName(font.FontFace);

            if (!string.IsNullOrWhiteSpace(src))
            {
                var strsrc = Path.GetExtension(src);
                if (!string.IsNullOrWhiteSpace(strsrc))
                {
                    ext = strsrc;
                }
            }

            if (string.IsNullOrEmpty(ext))
            {
                if (scheme == ExportNamingScheme.System)
                {
                    fileName = src;
                }
            }

            if (string.IsNullOrWhiteSpace(fileName))
            {
                fileName = $"{font.FamilyName.Trim()} {font.PreferredName.Trim()}{ext}";
            }

            return($"{Utils.Humanise(Path.GetFileNameWithoutExtension(fileName), false)}{Path.GetExtension(fileName).ToLower()}");
        }
Esempio n. 3
0
        public static string GetCharString(FontVariant variant)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendJoin(string.Empty, variant.GetCharacters().Select(c => c.Char));
            return(sb.ToString());
        }
Esempio n. 4
0
        /// <summary>
        /// Returns a list of Typographic Variants for a character supported by the font.
        /// </summary>
        public static List <TypographyFeatureInfo> GetCharacterVariants(FontVariant font, Models.Character character)
        {
            var textAnalyzer = new CanvasTextAnalyzer(character.Char, CanvasTextDirection.TopToBottomThenLeftToRight);
            KeyValuePair <CanvasCharacterRange, CanvasAnalyzedScript> analyzed = textAnalyzer.GetScript().First();

            List <TypographyFeatureInfo> supported = new List <TypographyFeatureInfo>
            {
                TypographyFeatureInfo.None
            };

            foreach (var feature in font.XamlTypographyFeatures)
            {
                if (feature == TypographyFeatureInfo.None)
                {
                    continue;
                }

                var    glyphs  = textAnalyzer.GetGlyphs(analyzed.Key, font.FontFace, 24, false, false, analyzed.Value);
                bool[] results = font.FontFace.GetTypographicFeatureGlyphSupport(analyzed.Value, feature.Feature, glyphs);

                if (results.Any(r => r))
                {
                    supported.Add(feature);
                }
            }

            return(supported);
        }
        public static (string Path, Rect Bounds) GetGeometry(
            float size,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTextLayoutAnalysis analysis,
            CanvasTypography typography)
        {
            using (CanvasGeometry geom = CreateGeometry(size, selectedVariant, selectedChar, analysis, typography))
            {
                /*
                 * Unfortunately this only constructs a monochrome path, if we want color
                 * Win2D does not yet expose the necessary API's to get the individual glyph
                 * layers that make up a color glyph.
                 *
                 * We'll need to handle this in C++/CX if we want to do this at some point.
                 */

                var bounds  = geom.ComputeBounds();
                var interop = SimpleIoc.Default.GetInstance <Interop>();
                var s       = interop.GetPathData(geom);

                var t = s.Transform.Translation;
                bounds = new Rect(t.X - bounds.Left, -bounds.Top + t.Y, bounds.Width, bounds.Height);
                return(s.Path, bounds);
            }
        }
        public static async void RequestExportFontFile(FontVariant variant)
        {
            var scheme  = ResourceHelper.AppSettings.ExportNamingScheme;
            var interop = Utils.GetInterop();

            if (DirectWrite.IsFontLocal(variant.FontFace))
            {
                string filePath = GetFileName(variant, scheme);
                string name     = Path.GetFileNameWithoutExtension(filePath);
                string ext      = Path.GetExtension(filePath);

                if (await PickFileAsync(name, Localization.Get("ExportFontFile/Text"), new[] { ext }, PickerLocationId.DocumentsLibrary) is StorageFile file)
                {
                    try
                    {
                        bool success = await TryWriteToFileAsync(variant, file);

                        Messenger.Default.Send(new AppNotificationMessage(true, new ExportFontFileResult(success, file)));
                        return;
                    }
                    catch
                    {
                    }
                }
            }

            Messenger.Default.Send(new AppNotificationMessage(true, new ExportFontFileResult(null, false)));
        }
        public static List <TypographyFeatureInfo> GetSupportedTypographyFeatures(FontVariant variant)
        {
            var features = DirectWrite.GetSupportedTypography(variant.FontFace).Values.ToList();
            var list     = features.Select(f => new TypographyFeatureInfo((CanvasTypographyFeatureName)f)).OrderBy(f => f.DisplayName).ToList();

            return(list);
        }
Esempio n. 8
0
        public static InstalledFont CreateDefault(DWriteFontFace face)
        {
            var font = new InstalledFont("");

            font.FontFace = face.FontFace;
            font._variants.Add(FontVariant.CreateDefault(face.FontFace));
            return(font);
        }
        private static string GetFileName(
            InstalledFont selectedFont,
            FontVariant selectedVariant,
            Character selectedChar,
            string ext)
        {
            var chr = GlyphService.GetCharacterDescription(selectedChar.UnicodeIndex, selectedVariant) ?? selectedChar.UnicodeString;

            return($"{selectedFont.Name} {selectedVariant.PreferredName} - {chr}.{ext}");
        }
Esempio n. 10
0
        /// <summary>
        /// Creates a FontAnalysis object for a FontVariant and ensures the custom
        /// search map for the font is loaded
        /// </summary>
        /// <param name="variant"></param>
        /// <returns></returns>
        public static FontAnalysis Analyze(FontVariant variant)
        {
            var analysis = new FontAnalysis(variant.FontFace);

            if (analysis.HasGlyphNames)
            {
                PrepareSearchMap(variant, analysis.GlyphNameMappings);
            }
            return(analysis);
        }
        private static async Task <bool> TryWriteToFileAsync(FontVariant font, StorageFile file)
        {
            try
            {
                using IRandomAccessStream s = await file.OpenAsync(FileAccessMode.ReadWrite);

                s.Size = 0;

                using IOutputStream o = s.GetOutputStreamAt(0);
                await DirectWrite.WriteToStreamAsync(font.FontFace, o);

                return(true);
            }
            catch { }

            return(false);
        }
Esempio n. 12
0
        /// <summary>
        /// Creates a FontAnalysis object for a FontVariant and ensures the custom
        /// search map for the font is loaded
        /// </summary>
        /// <param name="variant"></param>
        /// <returns></returns>
        public static FontAnalysis Analyze(FontVariant variant)
        {
            var analysis = new FontAnalysis(variant.FontFace);

            if (analysis.GlyphNames != null &&
                analysis.GlyphNames.Count > 0)
            {
                PrepareSearchMap(variant, analysis.GlyphNames.ToList());
            }

            // TODO : Parse Design & Script Language Tags
            // if (!string.IsNullOrWhiteSpace(analysis.DesignLanguages))
            // {
            //     var langs = analysis.DesignLanguages.Split(',');
            // }

            return(analysis);
        }
        private static async Task <bool> TryWriteToFileAsync(FontVariant font, StorageFile file)
        {
            try
            {
                using IRandomAccessStream s = await file.OpenAsync(FileAccessMode.ReadWrite).AsTask().ConfigureAwait(false);

                s.Size = 0;

                using IOutputStream o = s.GetOutputStreamAt(0);
                await DirectWrite.WriteToStreamAsync(font.FontFace, o).AsTask().ConfigureAwait(false);

                await s.FlushAsync(); // using statements force synchronous flushes

                return(true);
            }
            catch { }

            return(false);
        }
        public static async void RequestExportFontFile(FontVariant variant, CanvasTextLayoutAnalysis ana)
        {
            string name = Path.GetFileNameWithoutExtension(ana.FilePath);
            string ext  = Path.GetExtension(ana.FilePath);

            if (await PickFileAsync(name, Localization.Get("ExportFontFile/Text"), new[] { ext }, PickerLocationId.DocumentsLibrary) is StorageFile file)
            {
                try
                {
                    var  interop = SimpleIoc.Default.GetInstance <Interop>();
                    bool success = await interop.WriteToFileAsync(variant.FontFace, file);

                    Messenger.Default.Send(new AppNotificationMessage(true, new ExportFontFileResult(success, file)));
                }
                catch
                {
                    Messenger.Default.Send(new AppNotificationMessage(true, new ExportFontFileResult(false, file)));
                }
            }
        }
Esempio n. 15
0
        public static List <TypographyFeatureInfo> GetSupportedTypographyFeatures(FontVariant variant)
        {
            Dictionary <string, TypographyFeatureInfo> features = new Dictionary <string, TypographyFeatureInfo>();
            var analyzer = new CanvasTextAnalyzer(variant.GetCharString(), CanvasTextDirection.LeftToRightThenTopToBottom);

            {
                foreach (var script in analyzer.GetScript())
                {
                    foreach (var feature in variant.FontFace.GetSupportedTypographicFeatureNames(script.Value))
                    {
                        var info = new TypographyFeatureInfo(feature);
                        if (!features.ContainsKey(info.DisplayName))
                        {
                            features.Add(info.DisplayName, info);
                        }
                    }
                }
            }

            return(features.Values.OrderBy(f => f.DisplayName).ToList());
        }
Esempio n. 16
0
        private static void PrepareSearchMap(FontVariant variant, List <GlyphNameMap> names)
        {
            if (variant.SearchMap == null)
            {
                var idxs = variant.GetIndexes();
                var rng  = variant.FontFace.GetGlyphIndices(idxs);

                Dictionary <Character, GlyphNameMap> map = new Dictionary <Character, GlyphNameMap>();

                var list = variant.GetCharacters();
                for (int i = 0; i < list.Count; i++)
                {
                    var c       = list[i];
                    var mapping = names[rng[i]];
                    mapping.Name = mapping.Name.Replace("-", " ").Replace("_", " ");
                    map.Add(c, mapping);
                }

                variant.SearchMap = map;
            }
        }
Esempio n. 17
0
        private static void PrepareSearchMap(FontVariant variant, IReadOnlyDictionary <int, string> names)
        {
            if (variant.SearchMap == null)
            {
                uint[] uni = variant.GetGlyphUnicodeIndexes();
                int[]  gly = variant.FontFace.GetGlyphIndices(uni);
                IReadOnlyList <Character> chars = variant.GetCharacters();

                Dictionary <Character, string> map = new Dictionary <Character, string>();

                for (int i = 0; i < chars.Count; i++)
                {
                    Character c = chars[i];
                    if (names.TryGetValue(gly[i], out string mapping) && !string.IsNullOrEmpty(mapping))
                    {
                        map.Add(c, mapping);
                    }
                }

                variant.SearchMap = map;
            }
        }
        private static string GetFileName(FontVariant font, ExportNamingScheme scheme)
        {
            string fileName = null;
            string ext      = ".ttf";

            var src = DirectWrite.GetFileName(font.FontFace);

            if (!string.IsNullOrWhiteSpace(src))
            {
                ext = Path.GetExtension(src);
            }

            if (scheme == ExportNamingScheme.System)
            {
                fileName = src;
            }

            if (string.IsNullOrWhiteSpace(fileName))
            {
                fileName = $"{font.FamilyName} {font.PreferredName}{ext}";
            }

            return($"{Humanizer.To.SentenceCase.Transform(Path.GetFileNameWithoutExtension(fileName))}{Path.GetExtension(fileName).ToLower()}");
        }
        public static (string Path, Rect Bounds) GetGeometry(
            float size,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTextLayoutAnalysis analysis,
            CanvasTypography typography)
        {
            /*
             * Note: this only constructs the monochrome version
             * of the glyph.
             *
             * Drop into C++/CX for color / multi-variant glyphs.
             */

            using CanvasGeometry geom = CreateGeometry(size, selectedVariant, selectedChar, analysis, typography);
            var bounds  = geom.ComputeBounds();
            var interop = Utils.GetInterop();
            var s       = interop.GetPathData(geom);

            var t = s.Transform.Translation;

            bounds = new Rect(t.X - bounds.Left, -bounds.Top + t.Y, bounds.Width, bounds.Height);
            return(s.Path, bounds);
        }
        public static async Task ExportSvgAsync(
            ExportStyle style,
            InstalledFont selectedFont,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTypography typography)
        {
            try
            {
                string name = GetFileName(selectedFont, selectedVariant, selectedChar, "svg");
                if (await PickFileAsync(name, "SVG", new[] { ".svg" }) is StorageFile file)
                {
                    CachedFileManager.DeferUpdates(file);
                    var device = Utils.CanvasDevice;

                    var textColor = style == ExportStyle.Black ? Colors.Black : Colors.White;

                    /* SVG Exports render at fixed size - but a) they're vectors, and b) they're
                     * inside an auto-scaling viewport. So rendersize is *largely* pointless */
                    float canvasH = 1024f, canvasW = 1024f, fontSize = 1024f;

                    using (CanvasTextLayout layout = new CanvasTextLayout(device, $"{selectedChar.Char}", new CanvasTextFormat
                    {
                        FontSize = fontSize,
                        FontFamily = selectedVariant.Source,
                        FontStretch = selectedVariant.FontFace.Stretch,
                        FontWeight = selectedVariant.FontFace.Weight,
                        FontStyle = selectedVariant.FontFace.Style,
                        HorizontalAlignment = CanvasHorizontalAlignment.Center
                    }, canvasW, canvasH))
                    {
                        layout.SetTypography(0, 1, typography);

                        using (CanvasGeometry temp = CanvasGeometry.CreateText(layout))
                        {
                            var    b     = temp.ComputeBounds();
                            double scale = Math.Min(1, Math.Min(canvasW / b.Width, canvasH / b.Height));

                            Matrix3x2 transform =
                                Matrix3x2.CreateTranslation(new Vector2((float)-b.Left, (float)-b.Top))
                                * Matrix3x2.CreateScale(new Vector2((float)scale));

                            using (CanvasGeometry geom = temp.Transform(transform))
                            {
                                /*
                                 * Unfortunately this only constructs a monochrome path, if we want color
                                 * Win2D does not yet expose the neccessary API's to get the individual glyph
                                 * layers that make up a colour glyph.
                                 *
                                 * We'll need to handle this in C++/CX if we want to do this at some point.
                                 */

                                SVGPathReciever rc = new SVGPathReciever();
                                geom.SendPathTo(rc);

                                Rect bounds = geom.ComputeBounds();
                                using (CanvasSvgDocument document = Utils.GenerateSvgDocument(device, bounds.Width, bounds.Height, rc))
                                {
                                    ((CanvasSvgNamedElement)document.Root.FirstChild).SetColorAttribute("fill", textColor);
                                    await Utils.WriteSvgAsync(document, file);
                                }
                            }
                        }
                    }

                    await CachedFileManager.CompleteUpdatesAsync(file);
                }
            }
            catch (Exception ex)
            {
                await SimpleIoc.Default.GetInstance <IDialogService>()
                .ShowMessageBox(ex.Message, Localization.Get("SaveImageError"));
            }
        }
        public static async Task <ExportResult> ExportSvgAsync(
            ExportStyle style,
            InstalledFont selectedFont,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTextLayoutAnalysis analysis,
            CanvasTypography typography)
        {
            try
            {
                string name = GetFileName(selectedFont, selectedVariant, selectedChar, "svg");
                if (await PickFileAsync(name, "SVG", new[] { ".svg" }) is StorageFile file)
                {
                    CachedFileManager.DeferUpdates(file);

                    CanvasDevice device    = Utils.CanvasDevice;
                    Color        textColor = style == ExportStyle.Black ? Colors.Black : Colors.White;


                    // If COLR format (e.g. Segoe UI Emoji), we have special export path.
                    if (style == ExportStyle.ColorGlyph && analysis.HasColorGlyphs && !analysis.GlyphFormats.Contains(GlyphImageFormat.Svg))
                    {
                        NativeInterop interop = Utils.GetInterop();
                        List <string> paths   = new List <string>();
                        Rect          bounds  = Rect.Empty;

                        foreach (var thing in analysis.Indicies)
                        {
                            var path = interop.GetPathDatas(selectedVariant.FontFace, thing.ToArray()).First();
                            paths.Add(path.Path);

                            if (!path.Bounds.IsEmpty)
                            {
                                var left   = Math.Min(bounds.Left, path.Bounds.Left);
                                var top    = Math.Min(bounds.Top, path.Bounds.Top);
                                var right  = Math.Max(bounds.Right, path.Bounds.Right);
                                var bottom = Math.Max(bounds.Bottom, path.Bounds.Bottom);
                                bounds = new Rect(
                                    left,
                                    top,
                                    right - left,
                                    bottom - top);
                            }
                        }

                        using (CanvasSvgDocument document = Utils.GenerateSvgDocument(device, bounds, paths, analysis.Colors, invertBounds: false))
                        {
                            await Utils.WriteSvgAsync(document, file);
                        }

                        return(new ExportResult(true, file));
                    }



                    var data = GetGeometry(1024, selectedVariant, selectedChar, analysis, typography);
                    async Task SaveMonochromeAsync()
                    {
                        using CanvasSvgDocument document = Utils.GenerateSvgDocument(device, data.Bounds, data.Path, textColor);
                        await Utils.WriteSvgAsync(document, file);
                    }

                    // If the font uses SVG glyphs, we can extract the raw SVG from the font file
                    if (analysis.GlyphFormats.Contains(GlyphImageFormat.Svg))
                    {
                        string  str = null;
                        IBuffer b   = GetGlyphBuffer(selectedVariant.FontFace, selectedChar.UnicodeIndex, GlyphImageFormat.Svg);
                        if (b.Length > 2 && b.GetByte(0) == 31 && b.GetByte(1) == 139)
                        {
                            using var stream = b.AsStream();
                            using var gzip   = new GZipStream(stream, CompressionMode.Decompress);
                            using var reader = new StreamReader(gzip);
                            str = reader.ReadToEnd();
                        }
                        else
                        {
                            using var dataReader       = DataReader.FromBuffer(b);
                            dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                            str = dataReader.ReadString(b.Length);
                        }

                        if (str.StartsWith("<?xml"))
                        {
                            str = str.Remove(0, str.IndexOf(">") + 1);
                        }

                        str = str.TrimStart();

                        try
                        {
                            using (CanvasSvgDocument document = CanvasSvgDocument.LoadFromXml(Utils.CanvasDevice, str))
                            {
                                // We need to transform the SVG to fit within the default document bounds, as characters
                                // are based *above* the base origin of (0,0) as (0,0) is the Baseline (bottom left) position for a character,
                                // so by default a will appear out of bounds of the default SVG viewport (towards top left).

                                //if (!document.Root.IsAttributeSpecified("viewBox")) // Specified viewbox requires baseline transform?
                                {
                                    // We'll regroup all the elements inside a "g" / group tag,
                                    // and apply a transform to the "g" tag to try and put in
                                    // in the correct place. There's probably a more accurate way
                                    // to do this by directly setting the root viewBox, if anyone
                                    // can find the correct calculation...

                                    List <ICanvasSvgElement> elements = new List <ICanvasSvgElement>();

                                    double minTop    = 0;
                                    double minLeft   = double.MaxValue;
                                    double maxWidth  = double.MinValue;
                                    double maxHeight = double.MinValue;

                                    void ProcessChildren(CanvasSvgNamedElement root)
                                    {
                                        CanvasSvgNamedElement ele = root.FirstChild as CanvasSvgNamedElement;

                                        while (true)
                                        {
                                            CanvasSvgNamedElement next = root.GetNextSibling(ele) as CanvasSvgNamedElement;
                                            if (ele.Tag == "g")
                                            {
                                                ProcessChildren(ele);
                                            }
                                            else if (ele.Tag == "path")
                                            {
                                                // Create a XAML geometry to try and find the bounds of each character
                                                // Probably more efficient to do in Win2D, but far less code to do with XAML.
                                                Geometry gm = XamlBindingHelper.ConvertValue(typeof(Geometry), ele.GetStringAttribute("d")) as Geometry;
                                                minTop    = Math.Min(minTop, gm.Bounds.Top);
                                                minLeft   = Math.Min(minLeft, gm.Bounds.Left);
                                                maxWidth  = Math.Max(maxWidth, gm.Bounds.Width);
                                                maxHeight = Math.Max(maxHeight, gm.Bounds.Height);
                                            }
                                            ele = next;
                                            if (ele == null)
                                            {
                                                break;
                                            }
                                        }
                                    }

                                    ProcessChildren(document.Root);

                                    double top  = minTop < 0 ? minTop : 0;
                                    double left = minLeft;
                                    document.Root.SetRectangleAttribute("viewBox", new Rect(left, top, data.Bounds.Width, data.Bounds.Height));
                                }

                                await Utils.WriteSvgAsync(document, file);
                            }
                        }
                        catch
                        {
                            // Certain fonts seem to have their SVG glyphs encoded with... I don't even know what encoding.
                            // for example: https://github.com/adobe-fonts/emojione-color
                            // In these cases, fallback to monochrome black
                            await SaveMonochromeAsync();
                        }
                    }
                    else
                    {
                        await SaveMonochromeAsync();
                    }

                    await CachedFileManager.CompleteUpdatesAsync(file);

                    return(new ExportResult(true, file));
                }
            }
            catch (Exception ex)
            {
                await SimpleIoc.Default.GetInstance <IDialogService>()
                .ShowMessageBox(ex.Message, Localization.Get("SaveImageError"));
            }

            return(new ExportResult(false, null));
        }
 private static string CleanFileName(FontVariant font, string fileName)
 {
     fileName = fileName ?? $"{font.FamilyName} {font.PreferredName}.ttf";
     return($"{Humanizer.To.SentenceCase.Transform(Path.GetFileNameWithoutExtension(fileName))}{Path.GetExtension(fileName).ToLower()}");
 }
        public static async Task <ExportResult> ExportPngAsync(
            ExportStyle style,
            InstalledFont selectedFont,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTextLayoutAnalysis analysis,
            CanvasTypography typography,
            AppSettings settings)
        {
            try
            {
                string name = GetFileName(selectedFont, selectedVariant, selectedChar, "png");
                if (await PickFileAsync(name, "PNG Image", new[] { ".png" }) is StorageFile file)
                {
                    CachedFileManager.DeferUpdates(file);

                    if (analysis.GlyphFormats.Contains(GlyphImageFormat.Png))
                    {
                        IBuffer buffer = GetGlyphBuffer(selectedVariant.FontFace, selectedChar.UnicodeIndex, GlyphImageFormat.Png);
                        await FileIO.WriteBufferAsync(file, buffer);
                    }
                    else
                    {
                        var device   = Utils.CanvasDevice;
                        var localDpi = 96; //Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi;

                        var canvasH = (float)settings.PngSize;
                        var canvasW = (float)settings.PngSize;

                        using var renderTarget = new CanvasRenderTarget(device, canvasW, canvasH, localDpi);
                        using (var ds = renderTarget.CreateDrawingSession())
                        {
                            ds.Clear(Colors.Transparent);
                            var d = settings.PngSize;
                            var r = settings.PngSize / 2;

                            var textColor = style == ExportStyle.Black ? Colors.Black : Colors.White;
                            var fontSize  = (float)d;

                            using CanvasTextLayout layout = new CanvasTextLayout(device, $"{selectedChar.Char}", new CanvasTextFormat
                            {
                                FontSize            = fontSize,
                                FontFamily          = selectedVariant.Source,
                                FontStretch         = selectedVariant.FontFace.Stretch,
                                FontWeight          = selectedVariant.FontFace.Weight,
                                FontStyle           = selectedVariant.FontFace.Style,
                                HorizontalAlignment = CanvasHorizontalAlignment.Center,
                                Options             = style == ExportStyle.ColorGlyph ? CanvasDrawTextOptions.EnableColorFont : CanvasDrawTextOptions.Default
                            }, canvasW, canvasH);

                            if (style == ExportStyle.ColorGlyph)
                            {
                                layout.Options = CanvasDrawTextOptions.EnableColorFont;
                            }

                            layout.SetTypography(0, 1, typography);

                            var    db    = layout.DrawBounds;
                            double scale = Math.Min(1, Math.Min(canvasW / db.Width, canvasH / db.Height));
                            var    x     = -db.Left + ((canvasW - (db.Width * scale)) / 2d);
                            var    y     = -db.Top + ((canvasH - (db.Height * scale)) / 2d);

                            ds.Transform =
                                Matrix3x2.CreateTranslation(new Vector2((float)x, (float)y))
                                * Matrix3x2.CreateScale(new Vector2((float)scale));

                            ds.DrawTextLayout(layout, new Vector2(0), textColor);
                        }

                        using var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite);

                        fileStream.Size = 0;
                        await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
                    }

                    await CachedFileManager.CompleteUpdatesAsync(file);

                    return(new ExportResult(true, file));
                }
            }
            catch (Exception ex)
            {
                await SimpleIoc.Default.GetInstance <IDialogService>()
                .ShowMessageBox(ex.Message, Localization.Get("SaveImageError"));
            }

            return(new ExportResult(false, null));
        }