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)); } }
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()}"); }
public static string GetCharString(FontVariant variant) { StringBuilder sb = new StringBuilder(); sb.AppendJoin(string.Empty, variant.GetCharacters().Select(c => c.Char)); return(sb.ToString()); }
/// <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); }
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}"); }
/// <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); }
/// <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))); } } }
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()); }
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; } }
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)); }