private void canvasControl_PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) { if (pointerDrag == null) { return; // Nothing to do } // Commit the shape into the document CanvasSvgNamedElement newChild = null; if (CurrentShapeType == ShapeType.Rectangle) { Rect r = pointerDrag.GetRectangle(); newChild = svgDocument.Root.CreateAndAppendNamedChildElement("rect"); newChild.SetFloatAttribute("x", (float)r.Left); newChild.SetFloatAttribute("y", (float)r.Top); newChild.SetFloatAttribute("width", (float)r.Width); newChild.SetFloatAttribute("height", (float)r.Height); } else if (CurrentShapeType == ShapeType.Ellipse) { var ellipse = pointerDrag.GetEllipse(); newChild = svgDocument.Root.CreateAndAppendNamedChildElement("ellipse"); newChild.SetFloatAttribute("cx", ellipse.CenterX); newChild.SetFloatAttribute("cy", ellipse.CenterY); newChild.SetFloatAttribute("rx", ellipse.RadiusX); newChild.SetFloatAttribute("ry", ellipse.RadiusY); } else if (CurrentShapeType == ShapeType.Circle) { var circle = pointerDrag.GetCircle(); newChild = svgDocument.Root.CreateAndAppendNamedChildElement("circle"); newChild.SetFloatAttribute("cx", circle.Center.X); newChild.SetFloatAttribute("cy", circle.Center.Y); newChild.SetFloatAttribute("r", circle.Radius); } else if (CurrentShapeType == ShapeType.Line) { var start = pointerDrag.StartLocation.ToVector2(); var end = pointerDrag.CurrentLocation.ToVector2(); newChild = svgDocument.Root.CreateAndAppendNamedChildElement("line"); newChild.SetFloatAttribute("x1", start.X); newChild.SetFloatAttribute("y1", start.Y); newChild.SetFloatAttribute("x2", end.X); newChild.SetFloatAttribute("y2", end.Y); } newChild.SetColorAttribute("fill", colorPicker.CurrentColor); newChild.SetColorAttribute("stroke", Colors.Black); newChild.SetFloatAttribute("stroke-width", 4.0f); pointerDrag = null; canvasControl.Invalidate(); }
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 void UseDefaultStroke(CanvasSvgNamedElement rect) { rect.SetColorAttribute("stroke", Colors.Black); rect.SetFloatAttribute("stroke-width", 5); }